叱¶
概述¶
HOOT 是一个用 Owl 编写的测试框架,其主要特点是:
注册并运行测试和测试套件;
显示直观的界面来查看和过滤测试结果;
提供与 DOM 交互的方法来模拟用户操作;
提供低级帮助程序,允许模拟各种全局对象。
因此,它已作为 lib/ 集成到 Odoo 代码库中并导出 2 个主要模块:
@odoo/hoot-dom:(可在游览中使用)助手:查询 DOM 中的元素,例如
queryAll()和waitFor();
@odoo/hoot:(仅在单元测试中使用)所有测试框架功能:test、describe和expect测试钩子,如
after和afterEach使用
getFixture处理夹具日期和时间处理,如
mockDate或advanceTime通过
mockFetch()或mockWebSocket()模拟网络响应@odoo/hoot-dom导出的每个助手
注解
文档的这一部分并不是要列出 Hoot 中可用的 所有 帮助程序(完整列表可以在 @odoo/hoot 模块本身中找到)。这里的目标是展示最常用的帮助器,并证明导致测试框架当前形状的一些决策的合理性。
运行测试¶
在 Odoo 中,可以通过访问 /web/tests URL 来运行前端单元测试。调用测试运行程序的大部分设置已经就位:
web.assets_unit_tests包已定义,并选取大多数插件中定义的所有测试;start.hoot.js文件负责使用其导出的start入口点函数调用测试运行器。
当进入测试页面时,测试将按顺序运行,结果将显示在控制台和 GUI 中(如果未在 headless 模式下运行)。
跑步者选项¶
运行器可以配置为:
通过界面(带有配置下拉列表和搜索栏);
或通过 URL 查询参数(例如
?headless以无头模式运行)。
以下是跑步者可用选项的列表:
bail测试运行程序将停止之前失败的测试数量。假值(包括 0)意味着跑步者永远不应该被中止。 (默认值:
0)
debugTest与
FILTER_SCHEMA.test过滤器相同,同时还将测试运行器置于“调试”模式。请参阅TestRunner.debug了解更多信息。 (默认值:false)
fps设置每秒帧数的值(这将转换为毫秒并在
advanceFrame中使用)
filter将根据其全名(包括其父套件)及其标签过滤匹配的测试/套件的搜索字符串。 (默认值:
"")
frameRate*估计*每秒渲染的帧数,在模拟动画帧时使用。 (默认值:
60fps)
fun减轻心情。 (默认值:
false)
headless是否呈现测试运行器用户界面。 (默认值:
false)
id专门运行的套件或测试的 ID。作业的 ID 是根据其全名确定性生成的。
loglevel测试运行程序使用的日志级别。级别越高,显示的日志越多:
0:仅显示运行者日志(默认)1:所有套件结果也会被记录2:还记录所有测试结果3:还记录每个测试的调试信息
manual页面加载后是否必须手动启动测试运行程序(默认为自动启动)。 (默认值:
false)
notrycatch删除每个测试的运行函数周围
try .. catch语句的安全性,以使错误冒泡到浏览器。 (默认值:false)
order确定测试执行的顺序:
"fifo":测试将按照文件系统中声明的顺序运行;"lifo":测试将以相反的顺序依次运行;"random":在其父套件中重新排列测试和套件。
preset测试运行程序运行的环境。该参数用于确定其他特征的默认值,即:
用户代理;
触摸支持;
视口的预期大小。
showdetail确定失败的测试必须如何在 UI 中展开。 (默认值:
"first-fail")
tag标记要专门运行的测试和套件的名称(不区分大小写)。 (默认:空)
timeout测试自动失败的持续时间(以毫秒为单位)。 (默认值:
5秒)
注解
当选择要运行的测试和套件时,会在 include 过滤器之间应用隐式 OR。这意味着添加更多包容性过滤器将导致运行更多测试。这适用于 filter、id 和 tag 过滤器(但是*排除*过滤器将从要运行的测试列表中删除匹配的测试)。
编写测试¶
测试¶
编写测试可以非常简单,因为只需使用名称和包含测试逻辑的函数调用 test 函数即可。
这是一个简单的例子:
import { expect, test } from "@odoo/hoot";
test("My first test", () => {
expect(2 + 2).toBe(4);
});
描述¶
大多数时候,测试并不那么简单。它们通常需要一些设置和拆卸,有时需要将它们组合在一个套件中。这就是 describe 函数发挥作用的地方。
以下是声明套件及其中的测试的方法:
import { describe, expect, test } from "@odoo/hoot";
describe("My first suite", () => {
test("My first test", () => {
expect(2 + 2).toBe(4);
});
});
重要
在 Odoo 中,所有测试文件都在隔离环境中运行,并包装在全局 describe 块中(套件的名称是测试文件的*路径*)。
考虑到这一点,您不需要在测试文件中声明套件,但如果您仍想出于组织或标记目的拆分文件的套件,您仍然可以在同一文件中声明子套件。
预计¶
expect 函数是框架的主要断言函数。它用于断言某个值或对象是其预期的状态或处于其应有的状态。为此,它提供了一些修饰符和广泛的匹配器。
修饰符¶
expect 修饰符是一个 getter,它返回另一组以特定方式运行的*更改的*匹配器。
not反转以下匹配器的结果:如果匹配器失败,则成功。
expect(true).not.toBe(false);
resolves在使用解析值运行以下匹配器之前,等待值 (
Promise) 被“解析”。await expect(Promise.resolve(42)).resolves.toBe(42);
rejects等待值 (
Promise) 被“拒绝”*,然后再运行以下带有拒绝原因的匹配器。await expect(Promise.reject("error")).rejects.toBe("error");
注解
resolves 和 rejects 修饰符仅在值为 Promise 时可用,并且将返回一个 Promise,该 Promise 将在断言完成后解析。
常规匹配器¶
匹配器决定对正在测试的值做什么。有些会按原样获取该值,而另一些会在对其执行断言之前“转换”该值(即 DOM 匹配器)。
请注意,所有匹配器的最后一个参数是一个带有附加选项的可选字典,其中可以给出自定义断言 message 以添加上下文/特异性。
第一个匹配器列表是基于原始或对象的,并且是最常见的:
- toBe(expected[, options])¶
期望接收到的值“严格等于”
expected值。参数
expected:anyoptions:{ message?: string }
示例
expect("foo").toBe("foo"); expect({ foo: 1 }).not.toBe({ foo: 1 });
- toBeCloseTo(expected[, options])¶
期望接收到的值*接近*
expected值,最多为给定的位数(默认为 2)。参数
expected:anyoptions:{ message?: string, digits?: number }
示例
expect(0.2 + 0.1).toBeCloseTo(0.3); expect(3.51).toBeCloseTo(3.5, { digits: 1 });
- toBeEmpty([options])¶
预计接收到的值为空:
iterable:没有项目object:没有钥匙node:没有内容(即没有值或文本)其他:虚假值(
false、0、""、null、undefined)
参数
options:{ message?: string }
示例
expect({}).toBeEmpty(); expect(["a", "b"]).not.toBeEmpty(); expect(queryOne("input")).toBeEmpty();
- toBeGreaterThan(min[, options])¶
预计收到的值“严格大于”
min。参数
min:numberoptions:{ message?: string }
示例
expect(5).toBeGreaterThan(-1); expect(4 + 2).toBeGreaterThan(5);
- toBeInstanceOf(cls[, options])¶
期望接收到的值是给定
cls的实例。参数
cls:Functionoptions:{ message?: string }
示例
expect({ foo: 1 }).not.toBeInstanceOf(Object); expect(document.createElement("div")).toBeInstanceOf(HTMLElement);
- toBeLessThan(max[, options])¶
预计收到的值“严格小于”
max。参数
max:numberoptions:{ message?: string }
示例
expect(5).toBeLessThan(10); expect(8 - 6).toBeLessThan(3);
- toBeOfType(type[, options])¶
期望收到的值为给定的
type。参数
type:stringoptions:{ message?: string }
示例
expect("foo").toBeOfType("string"); expect({ foo: 1 }).toBeOfType("object");
- toBeWithin(min, max[, options])¶
预计收到的值介于
min和max之间(包括两者)。参数
min:numbermax:numberoptions:{ message?: string }
示例
expect(3).toBeWithin(3, 9); expect(-8.5).toBeWithin(-20, 0); expect(100).toBeWithin(50, 100);
- toEqual(expected[, options])¶
期望收到的值“深度等于”
expected值。参数
expected:anyoptions:{ message?: string }
示例
expect(["foo"]).toEqual(["foo"]); expect({ foo: 1 }).toEqual({ foo: 1 });
- toHaveLength(length[, options])¶
期望接收到的值具有给定
length的长度。接收到的值可以是任意Iterable或Object。参数
length:numberoptions:{ message?: string }
示例
expect("foo").toHaveLength(3); expect([1, 2, 3]).toHaveLength(3); expect({ foo: 1, bar: 2 }).toHaveLength(2); expect(new Set([1, 2])).toHaveLength(2);
- toInclude(item[, options])¶
期望收到的值包含给定形状的
item。接收到的值可以是可迭代的或对象(如果它是对象,则
item应该是表示该对象中的条目的键或元组)。请注意,这不是严格的比较:该项目将与可迭代的每个项目进行深度相等匹配。
参数
item:anyoptions:{ message?: string }
示例
expect([1, 2, 3]).toInclude(2); expect({ foo: 1, bar: 2 }).toInclude("foo"); expect({ foo: 1, bar: 2 }).toInclude(["foo", 1]); expect(new Set([{ foo: 1 }, { bar: 2 }])).toInclude({ bar: 2 });
- toMatch(matcher[, options])¶
期望收到的值与给定的
matcher匹配。参数
matcher:string | number | RegExpoptions:{ message?: string }
示例
expect(new Error("foo")).toMatch("foo"); expect("a foo value").toMatch(/fo.*ue/);
- toThrow(matcher[, options])¶
期望接收到的
Function在调用后抛出错误。参数
matcher:string | number | RegExpoptions:{ message?: string }
示例
expect(() => { throw new Error("Woops!") }).toThrow(/woops/i); await expect(Promise.reject("foo")).rejects.toThrow("foo");
DOM 匹配器¶
下一个匹配器列表是基于节点的,用于断言节点或节点列表的状态。它们通常采用 custom selector 作为 expect 函数的参数(尽管也接受 Node 或 Node 的迭代)。
- toBeChecked([options])¶
预计收到的
Target为"checked",如果同名选项设置为true,则为"indeterminate"。参数
options:{ message?: string, indeterminate?: boolean }
示例
expect("input[type=checkbox]").toBeChecked();
- toBeDisplayed([options])¶
期望收到的
Target“显示”,这意味着:它有一个边界框;
它包含在根文档中。
参数
options:{ message?: string }
示例
expect(document.body).toBeDisplayed(); expect(document.createElement("div")).not.toBeDisplayed();
- toBeEnabled([options])¶
期望接收到的
Target被“启用”*,这意味着它与:enabled伪选择器匹配。参数
options:{ message?: string }
示例
expect("button").toBeEnabled(); expect("input[type=radio]").not.toBeEnabled();
- toBeVisible([options])¶
期望收到的
Target“可见”,这意味着:它有一个边界框;
它包含在根文档中;
它不会被 CSS 属性隐藏。
参数
options:{ message?: string }
示例
expect(document.body).toBeVisible(); expect("[style='opacity: 0']").not.toBeVisible();
- toHaveAttribute(attribute, value[, options])¶
期望接收到的
Target具有给定的属性集,并且该属性值与给定的value(如果有)匹配。参数
attribute:stringvalue:string | number | RegExpoptions:{ message?: string }
示例
expect("a").toHaveAttribute("href"); expect("script").toHaveAttribute("src", "./index.js");
- toHaveClass(className[, options])¶
期望收到的
Target具有给定的类名。参数
className:string | string[]options:{ message?: string }
示例
expect("button").toHaveClass("btn btn-primary"); expect("body").toHaveClass(["o_webclient", "o_dark"]);
- toHaveCount(amount[, options])¶
期望收到的
Target恰好包含amount元素。请注意,可以省略amount参数,在这种情况下,函数将期望*至少*一个元素。参数
amount:numberoptions:{ message?: string }
示例
expect(".o_webclient").toHaveCount(1); expect(".o_form_view .o_field_widget").toHaveCount(); expect("ul > li").toHaveCount(4);
- toHaveInnerHTML(expected[, options])¶
期望接收到的
Target的innerHTML与expected值匹配(格式化后)。参数
expected:string | RegExpoptions:{ message?: string, type?: "html" | "xml", tabSize?: number, keepInlineTextNodes?: boolean }
示例
expect(".my_element").toHaveInnerHTML(` Some <strong>text</strong> `);
- toHaveOuterHTML(expected[, options])¶
期望接收到的
Target的outerHTML与expected值匹配(格式化后)。参数
expected:string | RegExpoptions:{ message?: string, type?: "html" | "xml", tabSize?: number, keepInlineTextNodes?: boolean }
示例
expect(".my_element").toHaveOuterHTML(` <div class="my_element"> Some <strong>text</strong> </div> `);
- toHaveProperty(property, value[, options])¶
期望接收到的
Target的给定属性值与给定的value匹配。如果未给出值:匹配器将检查目标上是否存在给定属性。参数
property:stringvalue:anyoptions:{ message?: string }
示例
expect("button").toHaveProperty("tabIndex", 0); expect("input").toHaveProperty("ontouchstart"); expect("script").toHaveProperty("src", "./index.js");
- toHaveRect(rect[, options])¶
期望接收到的
Target的DOMRect与给定的rect对象匹配。rect对象可以是:一个
DOMRect对象;CSS 选择器字符串(获取*唯一*匹配元素的矩形);
一个节点。
如果生成的
rect值是一个节点,则将比较两个节点的矩形。参数
rect:Partial<DOMRect> | Targetoptions:{ message?: string, trimPadding?: boolean }
示例
expect("button").toHaveRect({ x: 20, width: 100, height: 50 }); expect("button").toHaveRect(".container");
- toHaveStyle(style[, options])¶
期望收到的
Target与给定的样式属性匹配。参数
style:string | Record<string, string | RegExp>options:{ message?: string }
示例
expect("button").toHaveStyle({ color: "red" }); expect("p").toHaveStyle("text-align: center");
- toHaveText(text[, options])¶
预计收到的
Target的text内容为:严格等于给定字符串;
匹配给定的正则表达式。
注意:
innerHTML用于检索文本内容以考虑 CSS 可见性。这也意味着子元素中的文本值将使用换行符作为分隔符来连接。参数
text:string | RegExpoptions:{ message?: string, raw?: boolean }
示例
expect("p").toHaveText("lorem ipsum dolor sit amet"); expect("header h1").toHaveText(/odoo/i);
- toHaveValue(value[, options])¶
预计收到的
Target的值为:严格等于给定的字符串或数字;
匹配给定的正则表达式;
包含与给定
files列表匹配的文件对象。
参数
value:anyoptions:{ message?: string }
示例
expect("input[type=email]").toHaveValue("john@doe.com"); expect("input[type=file]").toHaveValue(new File(["foo"], "foo.txt")); expect("select[multiple]").toHaveValue(["foo", "bar"]);
静态方法¶
expect 辅助函数还包含静态方法,可用于运行独立的测试流程,该流程在某一时刻不绑定到一个特定值。
这些方法主要用于记录当前测试范围内的步骤或错误,并在以后进行评估。
- expect.assertions(expected)¶
- 参数
expected (
number()) –
预计当前测试有
expected数量的断言。该数字不能小于 1。注解
通常首选使用
expect.step()和expect.verifySteps()代替,因为它更可靠并且允许进行更广泛的测试。
- expect.errors(expected)¶
- 参数
expected (
number()) –
预计当前测试的错误数为
expected。这也意味着从调用此函数的那一刻起,测试将接受一定数量的错误,然后才会被视为失败。
- expect.step(value)¶
- 参数
value (
unknown()) –
为当前测试注册一个步骤,可由
expect.verifySteps()使用。未使用的步骤将使测试失败。
- expect.verifyErrors(errors[, options])¶
- 参数
errors (
unknown[]()) –options (
{ message?: string }()) –
- 返回
boolean
期望接收到的匹配器与自测试开始或上次调用
expect.verifyErrors()以来抛出的错误相匹配。调用此匹配器将重置当前错误列表。expect.verifyErrors([/RPCError/, /Invalid domain AST/]);
- expect.verifySteps(steps[, options])¶
- 参数
steps (
unknown[]()) –options (
{ ignoreOrder?: boolean, message?: string, partial?: boolean }()) –
- 返回
boolean
期望接收到的步骤等于自测试开始或上次调用
expect.verifySteps()以来发出的步骤。调用此匹配器将重置当前步骤的列表。expect.step("web_read_group"); expect.step([1, 2]); expect.verifySteps(["web_read_group", [1, 2]]);
- expect.waitForErrors(errors[, options])¶
- 参数
errors (
unknown[]()) –options (
{ message?: string }()) –
- 返回
Promise<boolean>
与
expect.verifyErrors()相同,但如果尚未捕获错误,则不会立即失败,而是等待一定的超时(默认值:2000ms)以允许稍后捕获错误。最初、超时结束时以及每次检测到错误时都会执行检查。
fetch("invalid/url"); await expect.waitForErrors([/RPCError/]);
- expect.waitForSteps(steps[, options])¶
- 参数
steps (
unknown[]()) –options (
{ ignoreOrder?: boolean, message?: string, partial?: boolean }()) –
- 返回
Promise<boolean>
与
expect.verifySteps()相同,但如果尚未注册步骤,则不会立即失败,而是等待一定的超时(默认值:2000ms)以允许稍后注册步骤。最初、超时结束时以及每次注册步骤时都会执行检查。
// ... step on each 'web_read_group' call fetch(".../call_kw/web_read_group"); await expect.waitForSteps(["web_read_group"]);
DOM:查询¶
自定义 DOM 选择器¶
这里有一个关于 Hoot 中 DOM 选择器的简短部分,因为它们支持额外的伪类,这些伪类可用于基于非标准功能定位元素,例如文本内容或文档中的全局位置。
:contains(text)匹配文本内容与给定
text匹配的节点给定的 text 支持正则表达式语法(例如
:contains(/^foo.+/))并且不区分大小写(除非在正则表达式末尾使用i标志)
:displayed匹配*“显示”*的节点(参见`isDisplayed`)
:empty匹配具有空内容(值或文本内容)的节点
:eq(n)根据其全局位置(从 0 开始的索引)返回第 n 个节点;
:first返回与选择器匹配的第一个节点(在整个文档中)
:focusable匹配可以*“聚焦”*的节点(参见`isFocusable`)
:hidden匹配 不 “可见” 的节点(参见
isVisible)
:iframe匹配属于
<iframe>元素的节点,如果就绪则返回其body
:last返回与选择器匹配的最后一个节点(在整个文档中)
:selected匹配选定的节点(例如
<option>元素)
:shadow匹配具有影子根的节点,并返回其影子根
:scrollable匹配可滚动的节点(参见
isScrollable)
:value(text)匹配其值与给定
text匹配的节点给定的 text 支持正则表达式语法(例如
:value(/^foo.+/))并且不区分大小写(除非在正则表达式末尾使用i标志)
:visible匹配“可见”* 的节点(参见
isVisible)
查询和节点属性助手¶
Hoot 提供了帮助程序,以简化且优雅的方式查询节点及其某些属性。这主要可以通过使用 queryX 帮助程序来完成:
- queryAll(target[, options])¶
返回与给定
Target匹配的节点列表。该函数可以用作 template literal tag (仅支持不带选项的字符串选择器)或以通常的方式调用。目标可以是:
一个
Node(或一个可迭代的节点),或Window对象;一个
Document对象(将被转换为其主体);表示 custom selector 的字符串(将从
root选项中查询)。
可以指定
options对象来过滤 1 结果:count:要匹配的确切节点数(如果节点数不匹配则抛出错误);displayed:节点是否必须“显示”(参见`isDisplayed`);focusable:节点是否必须“可聚焦”(参见isFocusable);root:查询选择器的根节点(默认为当前夹具);visible:节点是否必须“可见”(请参阅`isVisible`)。 * 此选项意味着displayed
- 1
这些过滤器(
count和root除外)实现与在给定选择器字符串的最后组上使用其同音伪类相同的结果,例如:// These 2 will return the same result queryAll`ul > li:visible`; queryAll("ul > li", { visible: true });
- 返回
Node[]
- queryAllAttributes(target, attribute[, options])¶
对给定的
target执行queryAll()并返回属性值列表。- 返回
string[]属性值列表
- queryAllProperties(target, property[, options])¶
对给定的
target执行queryAll()并返回属性值列表。- 返回
unknown[]属性值列表
- queryAllTexts(target[, options])¶
对给定的
target执行queryAll()并返回文本内容列表。- 返回
string[]文本内容列表
- queryAllValues(target[, options])¶
对给定的
target执行queryAll()并返回值列表。- 返回
string[]值列表
- queryAttribute(target, attribute[, options])¶
使用给定参数执行
queryOne()并返回匹配节点的给定attribute的值。- 返回
string属性值
- queryFirst(target[, options])¶
使用给定参数执行
queryAll()并返回第一个结果或null。- 返回
Node|null第一个匹配节点
- queryOne(target[, options])¶
使用给定参数执行
queryAll()以及强制count: 1选项,以确保只有一个节点与给定的Target匹配。返回的值是单个节点而不是节点列表。
- 返回
Node单个节点
- queryText(target[, options])¶
使用给定参数执行
queryOne()并返回匹配节点的 text。- 返回
string匹配节点的文本
- queryValue(target[, options])¶
使用给定参数执行
queryOne()并返回匹配节点的*值*。- 返回
string匹配节点的值
上述所有帮助程序都是同步的,这意味着它们将尝试立即查询节点。尽管某些用例要求元素等待任意时间,但由于 UI 获取和渲染的复杂性而提前未知。
Hoot 提供了 2 种方法来等待元素在一定时间范围内出现/消失(默认: 200 毫秒),适用于这种情况:
- waitFor(target[, options])¶
queryAll()和waitUntil()的组合:等待给定目标匹配 DOM 中的元素,并在第一个匹配节点出现时返回它(如果它已经存在,则立即返回)。- 返回
Promise<Node>包含第一个匹配节点
DOM:交互助手¶
除了查询元素之外,通常还需要与它们进行交互。因此,Hoot 提供了帮助程序来模拟元素上的各种用户交互。
根据它们的参数,它们可以分为两种类型:**基于指针的**交互助手和**其他**交互助手。
指针交互助手:¶
指针交互助手(例如 click() 或 drag())将模拟给定目标上的实际指针移动和事件,以及指针*应该*存在的任何先前元素。
- check(target[, options])¶
确保检查给定的
Target。如果未选中,则会在输入上模拟
click()。如果点击后仍然没有检查输入,则会抛出错误。- 返回
Promise<Event[]>
check("input[type=checkbox]"); // Checks the first <input> checkbox element
- click(target[, options])¶
对给定的
Target执行单击序列。事件顺序如下:
pointerdown[桌面]
mousedown[触摸]`touchstart`
[目标不是活动元素]
blur[目标可聚焦]
focuspointerup[桌面]
mouseup[触摸]`touchend`
clickdblclick如果点击未被阻止且当前点击计数为偶数
- 返回
Promise<Event[]>
click("button"); // Clicks on the first <button> element
- dblclick(target[, options])¶
在给定的
Target上执行两个click()序列。- 返回
Promise<Event[]>
dblclick("button"); // Double-clicks on the first <button> element
- drag(target[, options])¶
在给定的
Target上启动拖动序列。返回一组辅助函数来指导序列:
moveTo:将指针移动到给定的目标;drop:将拖动的元素放到给定的目标上(如果有);cancel:取消拖动序列。
- 返回
Promise<DragHelpers>
drag(".card:first").drop(".card:last"); // Drags the first card onto the last one drag(".card:first").moveTo(".card:last").drop(); // Same as above const { cancel, moveTo } = await drag(".card:first"); // Starts the drag sequence moveTo(".card:eq(3)"); // Moves the dragged card to the 4th card cancel(); // Cancels the drag sequence
- hover(target[, options])¶
在给定的
Target上执行悬停序列。事件顺序如下:
pointerover[桌面]
mouseoverpointerenter[桌面]
mouseenterpointermove[桌面]
mousemove[触摸]`touchmove`
- 返回
Promise<Event[]>
hover("button"); // Hovers the first <button> element
- pointerDown(target[, options])¶
在给定的
Target上执行向下指针操作。事件顺序如下:
pointerdown[桌面]
mousedown[触摸]`touchstart`
[目标不是活动元素]
blur[目标可聚焦]
focus
- 返回
Promise<Event[]>
pointerDown("button"); // Focuses to the first <button> element
- pointerUp(target[, options])¶
在给定的
Target上执行向上指针操作。事件顺序如下:
pointerup[桌面]
mouseup[触摸]`touchend`
- 返回
Promise<Event[]>
pointerUp("body"); // Triggers a pointer up on the <body> element
- scroll(target, position[, options])¶
在给定的
Target上执行滚动事件序列。事件顺序如下:
[桌面]
wheelscroll
- 返回
Promise<Event[]>
scroll("body", { y: 0 }); // Scrolls to the top of <body>
- setInputRange(target, value[, options])¶
将给定值设置为当前“input[type=range]”
Target。事件顺序如下:
pointerdowninputchangepointerup
- 返回
Promise<Event[]>
其他交互助手:¶
其他交互助手不会有 target 参数。不需要它,因为(例如)键盘上的按键是在当前*活动元素*上完成的。
- clear([options])¶
清除当前*活动元素*的值。
这是使用以下顺序完成的:
按
"Control"和"A"选择整个值;按
"Backspace"删除该值;(可选)按
"Enter"触发"change"事件。
- 返回
Promise<Event[]>
clear(); // Clears the value of the current active element
- edit(value[, options])¶
-
首先,清除输入值(如果有)
然后用给定值填充输入
- 返回
Promise<Event[]>
fill("foo"); // Types "foo" in the active element edit("Hello World"); // Replaces "foo" by "Hello World"
- fill(value[, options])¶
使用给定的
value填充当前*活动元素*。此帮助器适用于<input>和<textarea>元素,但"checkbox"和"radio"类型除外,应使用check帮助器进行选择。如果目标是可编辑输入,则其字符串
value将一次输入一个字符,每个字符都会生成相应的键盘事件序列。可以通过传递instantly选项来覆盖此行为,该选项将模拟control+v键盘序列,从而导致粘贴整个文本。请注意,给定值将附加到元素的当前值。
如果活动元素是
<input type="file"/>,则value应该是File/File对象的列表。- 返回
Promise<Event[]>
fill("Hello World"); // Types "Hello World" in the active element fill("Hello World", { instantly: true }); // Pastes "Hello World" in the active element fill(new File(["Hello World"], "hello.txt")); // Uploads a file named "hello.txt" with "Hello World" as content
- keyDown(keyStrokes[, options])¶
对当前*活动元素*执行按键序列。
事件顺序如下:
keydown
根据按下的键将执行其他操作:
Tab:聚焦下一个(或上一个shift)可聚焦元素;c:将当前选择复制到剪贴板;v:将当前剪贴板内容粘贴到当前元素;Enter:如果目标是<button type="button">或<form>元素,则提交表单;如果目标是<input>元素,则触发目标上的change事件;Space:如果目标是<input type="checkbox">元素,则在目标上触发click事件。
- 返回
Promise<Event[]>
keyDown(" "); // Space key
- keyUp(keyStrokes[, options])¶
对当前*活动元素*执行按键序列。
事件顺序如下:
keyup
- 返回
Promise<Event[]>
keyUp("Enter");
- leave([options])¶
对当前
Window执行离开序列。事件顺序如下:
pointermove[桌面]
mousemove[触摸]`touchmove`
pointerout[桌面]
mouseoutpointerleave[桌面]
mouseleave
- 返回
Promise<Event[]>
leave("button"); // Moves out of <button>
- press(keyStrokes[, options])¶
对当前*活动元素*执行键盘事件序列。
事件顺序如下:
keydownkeyup
- 返回
Promise<Event[]>
pointerDown("button[type=submit]"); // Moves focus to <button> keyDown("Enter"); // Submits the form keyDown("Shift+Tab"); // Focuses previous focusable element keyDown(["ctrl", "v"]); // Pastes current clipboard content
- resize([dimensions[, options]])¶
对当前
Window执行调整大小事件序列。事件顺序如下:
resize
目标将调整为给定尺寸,由
!important样式属性强制执行。- 返回
Promise<Event[]>
resize("body", { width: 1000, height: 500 }); // Resizes <body> to 1000x500
- select(value[, options])¶
对当前活动元素执行选择事件序列。此帮助程序仅适用于
<select>元素。事件顺序如下:
change
- 返回
Promise<Event[]>
click("select[name=country]"); // Focuses <select> element select("belgium"); // Selects the <option value="belgium"> element
- setInputFiles(files[, options])¶
将给定的
File列表提供给当前文件输入。仅当之前已与文件输入进行过交互(通过单击它)时,此帮助程序才起作用。- 返回
Promise<Event[]>
- unload([options])¶
在当前
Window上触发“beforeunload”事件。- 返回
Promise<Event[]>
模拟¶
默认情况下,Hoot 会模拟许多低级功能:clipboard、fetch、localStorage 等。这些模拟旨在不产生任何会干扰测试运行程序或其他测试上下文的副作用,同时仍然提供相同的接口以允许测试无缝依赖这些功能。
还需要(大多数时候)对这些功能强制执行操作或更改其行为以进行测试,因此存在与这些模拟功能交互的帮助程序。以下部分将列出主要的模拟功能以及与它们交互的方法。
时间¶
大多数异步功能都被嘲笑:“计时器”(setTimeout、setInterval 和 requestAnimationFrame)、Date 和 performance 都表现正常,但可以手动取消或加速,以大大缩短测试的实际持续时间。例如:所有“计时器”在每次测试结束时都被取消,以避免对下一次测试产生副作用。
重要
有 2 个主要的计时行为*不*被嘲笑:
Promise对象及相关API;OWL 的计时器函数:要等待 OWL 渲染函数,您必须求助于
animationFrame帮助程序。
网络¶
一般来说,我们不想在测试中执行实际的网络调用。为了确保这一点,对 fetch 和 XMLHttpRequest 的所有调用都已重新路由到指定给 mockFetch() 的函数。
注解
在 Odoo 中,这通常由模拟环境生成的 MockServer 隐式处理,即任何时候使用 mountWithCleanup 帮助器渲染组件。
相关帮手¶
- mockFetch([fetchFn])¶
通过将其替换为给定的
fetchFn来模拟 fetch 函数。fetchFn的返回值用作模拟获取的响应,如果不满足所需的格式,则将其包装在MockResponse对象中。mockFetch((input, init) => { if (input === "/../web_search_read") { return { records: [{ id: 3, name: "john" }] }; } // ... }); mockFetch((input, init) => { if (input === "/translations") { const translations = { "Hello, world!": "Bonjour, monde !", // ... }; return new Response(JSON.stringify(translations)); } });
- mockWebSocket([onWebSocketConnected])¶
激活模拟 WebSocket 类:
websocket 连接将由
window.fetch处理(参见:js:meth:mockFetch);创建 websocket 后将调用
onWebSocketConnected回调。
- mockWorker([onWorkerConnected])¶
激活模拟
Worker和SharedWorker类:然后,由工作 URL 获取的实际代码将由
window.fetch处理(请参阅:js:meth:mockFetch);创建工作线程后将调用
onWorkerConnected回调。
显着的全球特征¶
以下功能可能没有添加任何特定的模拟功能,但它们确实按预期工作,而不会更改它们的实际属性:
-
title和cookie都可以设置和读取,而不改变当前文档的实际属性。 -
historyAPI 被模拟并绑定到mockLocation对象以返回相同的值并提供一致性。 -
Hoot 返回一个
mockLocation对象来代替window.location使用,但这依赖于实际生产代码中间接的使用。重要
仅当在生产代码和调用
window.location之间设置间接时,此功能才有效。在 Odoo 中,它之所以有效,是因为@web/core/browser模块提供了这样的间接寻址,并且该模块在测试环境中被模拟以重定向到mockLocation对象。 -
最常用的导航器功能(例如
clipboardAPI 和userAgent)已被嘲笑以劫持其实际行为。它的permissions对象已绑定到权限 API 的全局模拟。 -
通知已被模拟,“通知”权限绑定到全局模拟权限 API。
-
权限可以通过赋予
"granted"或"denied"状态来启用或禁用其他 API。这可以通过mockPermission帮助程序来完成。 -
localStorage和sessionStorage都指向“虚拟”存储。 -
对于给定的测试/套件,可以使用
mockTouch()帮助程序全局强制激活或停用触摸功能。它将模拟窗口上ontouchstart等触摸处理程序的存在,以及设置为fine或coarse的"pointer"媒体。
相关帮手¶
- mockPermission(name[, value])¶
设置给定权限的给定值。这允许启用或阻止某些 API(请参阅 Permissions API)。
// Prevents the whole notification API from working mockPermission("notifications", "denied");
- mockTouch(setTouch)¶
在当前
Window中打开或关闭触摸功能。