元素仓库
一个轻量级,强壮的软件包 将你的UI选择器从测试代码中解开 通过将定位器外部化成一个中央的JSON寄存器,使你的测试自动化框架更清洁,更方便维护,并方便非开发者访问. 支持两者 剧作家 (网络) 财务报告和审定财务报表 Appium/WebriverIO(移动) 通过统一的API.
* 安装
npm i @civitas-cerebrum/element-repository
同伴依赖关系 :
用于网络测试, 安装 @playwright/test 或者说 playwright。用于移动/平台测试,安装 webdriverio。 。 。 .
- 这有什么用?
- 0 硬码选择器 : 将您的页面对象和步骤定义完全排除复杂的 DOM 查询.
- 平台不可知元素 API: 一个统一的
Element与交互、状态、取出、查询和等待方法进行接口,这些方法在Playwright和WebDriverIO之间同样有效. - 流利的动作链 : 对元素的多动作进行排序
element.action().waitForState('visible').click()。 。 。 . - 动态解析 : 自动将您的 JSON 配置转换为平台内置选择器 .
- 智能定位器 : 阵列的内置方法,随机元素选择,文本过滤,属性过滤,和能见度检查.
包件合同
此软件包拥有 元素获取 + 只有原始操作任何带有重试、倒置、选项驱动的变体、组成、匹配树或断言框架的都属于消费者包.
住在这里的是什么:
- 从 JSON 仓库中查找元素
- 这个
Element界面及其两个后端(WebElement为剧作家,PlatformElement用于Appium) ElementChain一个流畅的动作测序器Element原始人- 紧紧的一通电话包装
Locator/ WebdriverIO 元素(单击、填充、盘旋、检查、滚动IntoView等)
消费者的生活:
- 校验 DSL 、 断言错误格式化、 重试相匹配语义
- 指向截取自动反射 并击出回击,
{ withoutScrolling, ifPresent }修改 - 所列要素构成(文本/regex/带有Descendant过滤)
- 火柴树
steps.expect(...).text.toBe(...)),可见的Chan,步骤 API 外观
拇指规则——按执行分类,不是按名称分类. 其躯体为单一被包裹的原始呼号的方法是原始的并停留在这里(即使听起来是可验证的-y——例如, ElementChain.isPresent() 刚刚 element.isVisible() 在队列执行后). 一种重述、作曲或格式断言错误的方法是明智的,并会移动到消费者身上.
见 @civitas-cerebrum/element-interactions (网络)和 @civitas-cerebrum/singularity-engine (网络+移动+桌面)用于智能层.
QQ 配置
在您的工程中创建 JSON 文件以持有您的选择器 :
locators.json
{
"pages": [
{
"name": "HomePage",
"elements": [
{
"elementName": "search-input",
"selector": { "css": "input[name='search']" }
},
{
"elementName": "submit-button",
"selector": { "id": "btn-submit" }
}
]
},
{
"name": "ProductList",
"elements": [
{
"elementName": "product-cards",
"selector": { "xpath": "//article[@class='product']" }
}
]
}
]
}
多平台配置
使用 platform 字段来定义同一页面的平台特定选择器。 无页 platform 字段默认为 web。 。 。 .
{
"pages": [
{
"name": "LoginPage",
"platform": "web",
"elements": [
{ "elementName": "submitButton", "selector": { "css": "button.web-submit" } }
]
},
{
"name": "LoginPage",
"platform": "android",
"elements": [
{ "elementName": "submitButton", "selector": { "accessibility id": "SubmitBtn" } }
]
},
{
"name": "LoginPage",
"platform": "ios",
"elements": [
{ "elementName": "submitButton", "selector": { "predicate": "label == \"Submit\"" } }
]
}
]
}
支持选择键
这个 platform 每个页面对象的字段决定使用哪种选择器格式。 若为 platform ,默认为 web。 。 。 .
网络( Playwright)
| 密钥 | 决定: | 示例 |
|---|---|---|
css | css=<value> | "css": "button.primary" |
xpath | xpath=<value> | "xpath": "//button[@id='submit']" |
id | #<value> | "id": "btn-submit" |
text | text=<value> | "text": "Submit" |
testid | [data-testid='<value>'] | "testid": "login-btn" |
role | [role='<value>'] | "role": "button" |
placeholder | [placeholder='<value>'] | "placeholder": "Search..." |
label | [aria-label='<value>'] | "label": "Close" |
非网络/ 移动( Appium)
| 密钥 | 骆驼类别名 | 决定: | 示例 |
|---|---|---|---|
accessibility id | accessibilityId | ~<value> | "accessibility id": "LoginBtn" |
xpath | —— 说 | <value> (拉动) | "xpath": "//android.widget.Button" |
id | —— 说 | #<value> | "id": "submit-btn" |
uiautomator | androidUIAutomator | android=<value> | "uiautomator": "new UiSelector().text(\"Go\")" |
predicate | iOSNsPredicateString | -ios predicate string:<value> | "predicate": "label == \"Login\"" |
class chain | iOSClassChain | -ios class chain:<value> | "class chain": "**/XCUIElementTypeButton" |
class name | className | <value> (拉动) | "class name": "android.widget.EditText" |
所有包含空格的策略密钥也接受驼峰Case的别名(例如,
"accessibilityId"改为"accessibility id") (中文(简体) ).
选择器后退链
添加一个嵌入 fallback 选择器处理DOM变体(每个国家,每个品牌,每个渲染器)而无需重复回波条目. 当主线在短时间( 100 ms) 内匹配零元素时, 分辨率会进入 fallback ——递归.
{
"elementName": "loginButton",
"selector": {
"css": "[data-qa='login-button']",
"fallback": {
"role": "button",
"name": { "regex": "Log in|Iniciar sesión|Anmelden|Inloggen", "flags": "i" }
}
}
}
规则:
- 初始附属于雷波
defaultTimeout(15s default) ~ 倒置从未被咨询.waitFor解决元素出现的时刻,所以普通案件不支付超时费用. - 小学不附加于
defaultTimeout走进去selector.fallback。 。 。 . - 无条目
fallback键键完全跳过探测器——懒惰定位器像以前一样使用元素交互的动作-时间超时(30-60s). - 链为递归:
fallback.fallback.fallback这是合法的. - 倒回可以自由混合策略——CSS初级与角色+名倒回,id初级与xpath倒回等.
最坏情况 在节点匹配的N级链上: N × defaultTimeout. 在实务中倒计时涵盖了真实的DOM变体,因此第一或第二关在初级不匹配的页面上相匹配.
常见用途:
- 多国DOM变体 ——一个条目为每个国家服务,而不是针对具体国家的回购文件.
- 品牌特异性初级+通用倒置 - 更喜欢特定品牌
data-qa,降解为角色选择器. - 严重退化 ——在逐步推出期间对新旧DOM进行测试.
条目标记 : provisional 财务报告和审定财务报表 list
两个可选布尔键坐在旁边 elementName 财务报告和审定财务报表 selector。两个方法都无法改变元素的解决方式;它们记录了输入的内容 表示,所以工具化和消费包 就可以采取行动.
{
"elementName": "basketRows",
"list": true,
"selector": { "css": "[data-testid='basket-row']" }
},
{
"elementName": "discountLine",
"provisional": true,
"selector": { "role": "row", "name": { "regex": "discount", "flags": "i" } }
}
list——该条目为收录:其合同为"至少一对"(QQ 1),而非"确切为一元素". 消费者读它与getElementMeta(); 元素交互使用它verifyPresence编号 :getAll默认false。 。 。 .provisional——该条目尚未根据运行中的应用程序得到确认(例如从源代码或文档推断出).getProvisional()列出这些条目,以便管道能够确认或拒绝运送这些条目。 默认false。 。 。 .
当寄存器构建时(和静态),两个密钥都会被验证 ElementRepository.validate(data): 一个值,但不包括 true 编号 : false -比如绳子 "true" ——错误地点出每个违法者的名字 Page.element。 。 。 .
使用量
初始化
建筑师拿走 驱动程序 (打字笔) Page 或网络驱动程序 Browser作为第一个论点:
import { ElementRepository } from '@civitas-cerebrum/element-repository';
// Option A: Pass a file path (relative to project root)
const repo = new ElementRepository(page, 'tests/data/locators.json');
// Option B: Pass parsed JSON data directly
import locatorData from '../data/locators.json';
const repo = new ElementRepository(page, locatorData);
// Option C: With a custom timeout (default: 15000ms)
const repo = new ElementRepository(page, locatorData, 30000);
// Access the bound driver
const driver = repo.driver; // returns the Page/Browser passed to the constructor
获取元素
所有方法的使用 (elementName, pageName) 统一命令和返回 Element 对象 :
test('Search and select random product', async ({ page }) => {
const repo = new ElementRepository(page, 'tests/data/locators.json');
// Get a single element (defaults to first match)
const searchInput = await repo.get('search-input', 'HomePage');
await searchInput.fill('Trousers');
// Get with a specific strategy
const thirdCard = await repo.get('product-cards', 'ProductList', {
strategy: SelectionStrategy.INDEX, index: 2
});
// Select a random element from a list
const randomProduct = await repo.getRandom('product-cards', 'ProductList');
await randomProduct?.click();
// Find by text
const specificProduct = await repo.getByText('product-cards', 'ProductList', 'Blue Chinos');
await specificProduct?.click();
// Find by HTML attribute
const activeCard = await repo.getByAttribute('product-cards', 'ProductList', 'data-status', 'active');
// Get by index
const secondCard = await repo.getByIndex('product-cards', 'ProductList', 1);
// Get the first visible element
const visibleModal = await repo.getVisible('modal', 'HomePage');
// Filter by ARIA role
const navLink = await repo.getByRole('nav-links', 'HomePage', 'link');
});
流利的动作链
每个 Element 暴露一个 action(timeout?) 返回可导的方法 ElementChain 建会者. 队列多个动作并用单个执行它们 await数字 :
const element = await repo.get('submitButton', 'LoginPage');
// Interaction chain
await element.action(5000)
.waitForState('visible')
.click()
// Click without scrolling (for dropdown/flyout items)
await element.action()
.waitForState('attached')
.click({ withoutScrolling: true })
// Fill and verify
await element.action()
.fill('[email protected]')
.verifyText('[email protected]')
// Verification chain
await element.action(5000)
.verifyPresence()
.verifyText('Submit')
.verifyAttribute('type', 'submit')
// Extraction (terminal — returns value)
const text = await element.action().getText()
const href = await element.action().getAttribute('href')
const count = await element.action().getCount()
可用链式方法
相互作用: click(options?), (中文(简体) ), clickIfPresent(options?), (中文(简体) ), hover(), (中文(简体) ), fill(text), (中文(简体) ), clear(), (中文(简体) ), check(), (中文(简体) ), uncheck(), (中文(简体) ), doubleClick(), (中文(简体) ), scrollIntoView(), (中文(简体) ), pressSequentially(text, delay?), (中文(简体) ), dispatchEvent(event)
核查: verifyPresence(), (中文(简体) ), verifyAbsence(), (中文(简体) ), verifyText(expected?), (中文(简体) ), verifyTextContains(text), (中文(简体) ), verifyAttribute(name, value), (中文(简体) ), verifyEnabled(), (中文(简体) ), verifyDisabled(), (中文(简体) ), verifyChecked(), (中文(简体) ), verifyCount(options)
摘录( 直线): getText(), (中文(简体) ), getAttribute(name), (中文(简体) ), getInputValue(), (中文(简体) ), getCount(), (中文(简体) ), getRaw(), (中文(简体) ), isPresent()
正在等待中 : waitForState(state)
API 参考
元素仓库
constructor(driver, dataOrPath, defaultTimeout?)
| 参数 | 类型 | 说明 |
|---|---|---|
driver | any | 剧作家 Page 或网络驱动程序 Browser编号 :Driver 实例 |
dataOrPath | string | PageRepository | JSON 文件或解析的 JSON 对象的路径 |
defaultTimeout | number | 默认等待超时 ms( 默认 ): 15000(中文(简体) ) |
get(elementName, pageName, options?)
返回单个 Element。默认为第一个匹配。 过 ElementResolutionOptions 来控制选择 :
import { SelectionStrategy } from '@civitas-cerebrum/element-repository';
await repo.get('button', 'Page') // first (default)
await repo.get('button', 'Page', { strategy: SelectionStrategy.INDEX, index: 2 }) // by index
await repo.get('button', 'Page', { strategy: SelectionStrategy.RANDOM }) // random
await repo.get('button', 'Page', { strategy: SelectionStrategy.TEXT, value: 'Sub' }) // by text
await repo.get('button', 'Page', { strategy: SelectionStrategy.ATTRIBUTE,
attribute: 'data-id', value: 'row-2' }) // by attribute value
await repo.get('button', 'Page', { strategy: SelectionStrategy.ALL }) // un-narrowed (for counts)
一个未处理的战略值会丢出一个描述性错误 — 分辨率永远不会 当一个策略被请求时,悄悄地回到第一个匹配元素.
getAll(elementName, pageName)
返回所有匹配的数组 Element 对象.
getRandom(elementName, pageName, strict?)
随机从所有匹配中选择一个元素 .
getByText(elementName, pageName, desiredText, strict?)
返回包含指定文本的第一个元素。 先试一试,再回试.
getByAttribute(elementName, pageName, attribute, value, options?)
返回 HTML 属性符合给定值的第一个元素.
选项 : exact (布尔), strict (布尔)
getByIndex(elementName, pageName, index, strict?)
在指定的零基索引中返回元素.
getVisible(elementName, pageName, strict?)
返回第一个可见元素,过滤出隐藏的重复.
getByRole(elementName, pageName, role, strict?)
按元素过滤 role HTML属性.
getSelector(elementName, pageName)
返回一个平台格式的选择器字符串(同步).
getSelectorRaw(elementName, pageName)
回返 { strategy, value } ——原始选择器没有平台格式化.
getElementMeta(elementName, pageName)
回返 { provisional, list } 对条目,适用默认值(false。同步;只读存储器数据.
getProvisional()
回返 [{ pageName, elementName }] 每个标记条目 "provisional": true,按照仓库顺序.
ElementRepository.validate(data) (静态)
验证没有驱动程序(构造者也称它)的条目级密钥 。 掷出何时 provisional 或者说 list 以非布尔值存在.
getPagePlatform(pageName)
返回页面的平台字符串( E)'web', (中文(简体) ), 'android', (中文(简体) ), 'ios') (中文(简体) ).
严格模式
全体 get* 返回的方法 Element | null 接受选项 strict 参数 :
strict: false(默认)——返回null当找不到匹配时.strict: true抛出一个Error当找不到匹配时.
元素接口
全体 get* 方法返回一个 Element 一个平台不可知的界面, 包入一个 Playwright Locator (单位:千美元)WebElement或 WebDriverIO 元素(PlatformElement) (中文(简体) ).
相互作用方法
| 方法 | 说明 |
|---|---|
click(options?) | 单击元素。 回返 Promise<Element>。 。 。 . |
fill(text, options?) | 清取并取满文. 回返 Promise<Element>。 。 。 . |
clear(options?) | 清除元素的值。 回返 Promise<Element>。 。 。 . |
check(options?) | 检查复选框/收音机。 回返 Promise<Element>。 。 。 . |
uncheck(options?) | 取消检查复选框。 回返 Promise<Element>。 。 。 . |
hover(options?) | 绕过元素 回返 Promise<Element>。 。 。 . |
doubleClick(options?) | 双击. 回返 Promise<Element>。 。 。 . |
scrollIntoView(options?) | 卷入观想. 回返 Promise<Element>。 。 。 . |
pressSequentially(text, delay?, options?) | 按角色分类 回返 Promise<Element>。 。 。 . |
setInputFiles(filePath, options?) | 设置文件输入值 . 只有网络. |
dispatchEvent(event) | 发布DOM活动. 只有网络. |
所有交互方法都接受可选 { timeout?: number } 并返回元素用于简单的链条.
状态、采掘、查询和等待
| 方法 | 说明 |
|---|---|
isVisible() | 回返 true 如果可见. |
isEnabled() | 回返 true 如果启用的话. |
isChecked() | 回返 true 如果选中的话. |
raw() | 返回选择器字符串. |
textContent() | 返回文本内容或 null。 。 。 . |
getAttribute(name) | 返回属性值或 null。 。 。 . |
inputValue() | 返回当前输入值. |
count() | 返回匹配元素计数 . |
all() | 返回所有匹配元素的数组 . |
first() | 返回第一个匹配 . |
nth(index) | 在索引中返回元素. |
filter({ hasText }) | 按文本内容过滤. |
locateChild(selector) | 找到一个后人. |
waitFor(options?) | 等待状态 : "visible", (中文(简体) ), "hidden", (中文(简体) ), "attached", (中文(简体) ), "detached"。 。 。 . |
action(timeout?) | 返回流畅 ElementChain 建会者. |
QQ 类型安全
import { Element, isWeb, isPlatform } from '@civitas-cerebrum/element-repository';
const el = await repo.get('submitButton', 'LoginPage');
if (isWeb(el)) {
// Access the underlying Playwright Locator
await (el as WebElement).locator.click();
}
if (isPlatform(el)) {
// Access the WebDriverIO selector
console.log((el as PlatformElement).selector);
}
· 突破变化(v.1.0)
| 变动 | 移徙 |
|---|---|
需要构造器 driver 作为第一个参数 | new ElementRepository(data) → new ElementRepository(page, data) |
方法减少 driver编号 :page 第1个段落 | repo.get(page, 'Page', 'el') → repo.get('el', 'Page') |
参数顺序被翻转到 (elementName, pageName) | 在所有呼叫中交换参数 |
元素方法返回 Promise<Element> | 曾经 Promise<void> - 无需采取行动,除非你依赖 void |
raw() 返回实际选择符字符串 | 曾经 locator.toString() - 可能有所改进 |
许可证
麻省理工学院