概述

HOOT 是一个用 Owl 编写的测试框架,其主要特点是:

  • 注册并运行测试和测试套件;

  • 显示直观的界面来查看和过滤测试结果;

  • 提供与 DOM 交互的方法来模拟用户操作;

  • 提供低级帮助程序,允许模拟各种全局对象。

因此,它已作为 lib/ 集成到 Odoo 代码库中并导出 2 个主要模块:

  • @odoo/hoot-dom:(可在游览中使用)助手:

  • @odoo/hoot:(仅在单元测试中使用)所有测试框架功能:

    • testdescribeexpect

    • 测试钩子,如 afterafterEach

    • 使用 getFixture 处理夹具

    • 日期和时间处理,如 mockDateadvanceTime

    • 通过 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

    *估计*每秒渲染的帧数,在模拟动画帧时使用。 (默认值:60 fps)

  • 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。这意味着添加更多包容性过滤器将导致运行更多测试。这适用于 filteridtag 过滤器(但是*排除*过滤器将从要运行的测试列表中删除匹配的测试)。

编写测试

测试

编写测试可以非常简单,因为只需使用名称和包含测试逻辑的函数调用 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");
    

注解

resolvesrejects 修饰符仅在值为 Promise 时可用,并且将返回一个 Promise,该 Promise 将在断言完成后解析。

常规匹配器

匹配器决定对正在测试的值做什么。有些会按原样获取该值,而另一些会在对其执行断言之前“转换”该值(即 DOM 匹配器)。

请注意,所有匹配器的最后一个参数是一个带有附加选项的可选字典,其中可以给出自定义断言 message 以添加上下文/特异性。

第一个匹配器列表是基于原始或对象的,并且是最常见的:

toBe(expected[, options])

期望接收到的值“严格等于” expected 值。

  • 参数

    • expectedany

    • options{ message?: string }

  • 示例

    expect("foo").toBe("foo");
    expect({ foo: 1 }).not.toBe({ foo: 1 });
    
toBeCloseTo(expected[, options])

期望接收到的值*接近* expected 值,最多为给定的位数(默认为 2)。

  • 参数

    • expectedany

    • options{ 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:没有内容(即没有值或文本)

  • 其他:虚假值(false0""nullundefined

  • 参数

    • options{ message?: string }

  • 示例

    expect({}).toBeEmpty();
    expect(["a", "b"]).not.toBeEmpty();
    expect(queryOne("input")).toBeEmpty();
    
toBeGreaterThan(min[, options])

预计收到的值“严格大于”min

  • 参数

    • minnumber

    • options{ message?: string }

  • 示例

    expect(5).toBeGreaterThan(-1);
    expect(4 + 2).toBeGreaterThan(5);
    
toBeInstanceOf(cls[, options])

期望接收到的值是给定 cls 的实例。

  • 参数

    • clsFunction

    • options{ message?: string }

  • 示例

    expect({ foo: 1 }).not.toBeInstanceOf(Object);
    expect(document.createElement("div")).toBeInstanceOf(HTMLElement);
    
toBeLessThan(max[, options])

预计收到的值“严格小于”max

  • 参数

    • maxnumber

    • options{ message?: string }

  • 示例

    expect(5).toBeLessThan(10);
    expect(8 - 6).toBeLessThan(3);
    
toBeOfType(type[, options])

期望收到的值为给定的 type

  • 参数

    • typestring

    • options{ message?: string }

  • 示例

    expect("foo").toBeOfType("string");
    expect({ foo: 1 }).toBeOfType("object");
    
toBeWithin(min, max[, options])

预计收到的值介于 minmax 之间(包括两者)。

  • 参数

    • minnumber

    • maxnumber

    • options{ message?: string }

  • 示例

    expect(3).toBeWithin(3, 9);
    expect(-8.5).toBeWithin(-20, 0);
    expect(100).toBeWithin(50, 100);
    
toEqual(expected[, options])

期望收到的值“深度等于” expected 值。

  • 参数

    • expectedany

    • options{ message?: string }

  • 示例

    expect(["foo"]).toEqual(["foo"]);
    expect({ foo: 1 }).toEqual({ foo: 1 });
    
toHaveLength(length[, options])

期望接收到的值具有给定 length 的长度。接收到的值可以是任意 IterableObject

  • 参数

    • lengthnumber

    • options{ 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 应该是表示该对象中的条目的键或元组)。

请注意,这不是严格的比较:该项目将与可迭代的每个项目进行深度相等匹配。

  • 参数

    • itemany

    • options{ 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 匹配。

  • 参数

    • matcherstring | number | RegExp

    • options{ message?: string }

  • 示例

    expect(new Error("foo")).toMatch("foo");
    expect("a foo value").toMatch(/fo.*ue/);
    
toThrow(matcher[, options])

期望接收到的 Function 在调用后抛出错误。

  • 参数

    • matcherstring | number | RegExp

    • options{ message?: string }

  • 示例

    expect(() => { throw new Error("Woops!") }).toThrow(/woops/i);
    await expect(Promise.reject("foo")).rejects.toThrow("foo");
    

DOM 匹配器

下一个匹配器列表是基于节点的,用于断言节点或节点列表的状态。它们通常采用 custom selector 作为 expect 函数的参数(尽管也接受 NodeNode 的迭代)。

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();
    
toBeFocused([options])

期望收到的 Target 在其所有者文档中*“集中”*

  • 参数

    • options{ message?: string }

toBeVisible([options])

期望收到的 Target “可见”,这意味着:

  • 它有一个边界框;

  • 它包含在根文档中;

  • 它不会被 CSS 属性隐藏。

  • 参数

    • options{ message?: string }

  • 示例

    expect(document.body).toBeVisible();
    expect("[style='opacity: 0']").not.toBeVisible();
    
toHaveAttribute(attribute, value[, options])

期望接收到的 Target 具有给定的属性集,并且该属性值与给定的 value (如果有)匹配。

  • 参数

    • attributestring

    • valuestring | number | RegExp

    • options{ message?: string }

  • 示例

    expect("a").toHaveAttribute("href");
    expect("script").toHaveAttribute("src", "./index.js");
    
toHaveClass(className[, options])

期望收到的 Target 具有给定的类名。

  • 参数

    • classNamestring | string[]

    • options{ message?: string }

  • 示例

    expect("button").toHaveClass("btn btn-primary");
    expect("body").toHaveClass(["o_webclient", "o_dark"]);
    
toHaveCount(amount[, options])

期望收到的 Target 恰好包含 amount 元素。请注意,可以省略 amount 参数,在这种情况下,函数将期望*至少*一个元素。

  • 参数

    • amountnumber

    • options{ message?: string }

  • 示例

    expect(".o_webclient").toHaveCount(1);
    expect(".o_form_view .o_field_widget").toHaveCount();
    expect("ul > li").toHaveCount(4);
    
toHaveInnerHTML(expected[, options])

期望接收到的 TargetinnerHTMLexpected 值匹配(格式化后)。

  • 参数

    • expectedstring | RegExp

    • options{ message?: string, type?: "html" | "xml", tabSize?: number, keepInlineTextNodes?: boolean }

  • 示例

    expect(".my_element").toHaveInnerHTML(`
        Some <strong>text</strong>
    `);
    
toHaveOuterHTML(expected[, options])

期望接收到的 TargetouterHTMLexpected 值匹配(格式化后)。

  • 参数

    • expectedstring | RegExp

    • options{ 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 匹配。如果未给出值:匹配器将检查目标上是否存在给定属性。

  • 参数

    • propertystring

    • valueany

    • options{ message?: string }

  • 示例

    expect("button").toHaveProperty("tabIndex", 0);
    expect("input").toHaveProperty("ontouchstart");
    expect("script").toHaveProperty("src", "./index.js");
    
toHaveRect(rect[, options])

期望接收到的 TargetDOMRect 与给定的 rect 对象匹配。 rect 对象可以是:

  • 一个 DOMRect 对象;

  • CSS 选择器字符串(获取*唯一*匹配元素的矩形);

  • 一个节点。

如果生成的 rect 值是一个节点,则将比较两个节点的矩形。

  • 参数

    • rectPartial<DOMRect> | Target

    • options{ message?: string, trimPadding?: boolean }

  • 示例

    expect("button").toHaveRect({ x: 20, width: 100, height: 50 });
    expect("button").toHaveRect(".container");
    
toHaveStyle(style[, options])

期望收到的 Target 与给定的样式属性匹配。

  • 参数

    • stylestring | Record<string, string | RegExp>

    • options{ message?: string }

  • 示例

    expect("button").toHaveStyle({ color: "red" });
    expect("p").toHaveStyle("text-align: center");
    
toHaveText(text[, options])

预计收到的 Targettext 内容为:

  • 严格等于给定字符串;

  • 匹配给定的正则表达式。

注意:innerHTML 用于检索文本内容以考虑 CSS 可见性。这也意味着子元素中的文本值将使用换行符作为分隔符来连接。

  • 参数

    • textstring | RegExp

    • options{ message?: string, raw?: boolean }

  • 示例

    expect("p").toHaveText("lorem ipsum dolor sit amet");
    expect("header h1").toHaveText(/odoo/i);
    
toHaveValue(value[, options])

预计收到的 Target 的值为:

  • 严格等于给定的字符串或数字;

  • 匹配给定的正则表达式;

  • 包含与给定 files 列表匹配的文件对象。

  • 参数

    • valueany

    • options{ 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

这些过滤器(countroot 除外)实现与在给定选择器字符串的最后组上使用其同音伪类相同的结果,例如:

// 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> 包含第一个匹配节点

waitForNone(target[, options])

waitFor() 相反,等待给定的目标从 DOM 中消失。

返回

Promise<number> 包含匹配节点的数量

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

  • [目标可聚焦] focus

  • pointerup

  • [桌面] mouseup

  • [触摸]`touchend`

  • click

  • dblclick 如果点击未被阻止且当前点击计数为偶数

返回

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

  • [桌面] mouseover

  • pointerenter

  • [桌面] mouseenter

  • pointermove

  • [桌面] 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 上执行滚动事件序列。

事件顺序如下:

  • [桌面] wheel

  • scroll

返回

Promise<Event[]>

scroll("body", { y: 0 }); // Scrolls to the top of <body>
setInputRange(target, value[, options])

将给定值设置为当前“input[type=range]”Target

事件顺序如下:

  • pointerdown

  • input

  • change

  • pointerup

返回

Promise<Event[]>

uncheck(target[, options])

确保未选中给定的 Target

如果选中,则会在输入上触发 click() 。如果点击后仍然检查输入,则会抛出错误。

返回

Promise<Event[]>

uncheck("input[type=checkbox]"); // Unchecks the first <input> checkbox element

其他交互助手:

其他交互助手不会有 target 参数。不需要它,因为(例如)键盘上的按键是在当前*活动元素*上完成的。

clear([options])

清除当前*活动元素*的值。

这是使用以下顺序完成的:

  • "Control""A" 选择整个值;

  • "Backspace" 删除该值;

  • (可选)按 "Enter" 触发 "change" 事件。

返回

Promise<Event[]>

clear(); // Clears the value of the current active element
edit(value[, options])

clear()fill() 的组合:

  • 首先,清除输入值(如果有)

  • 然后用给定值填充输入

返回

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

  • [桌面] mouseout

  • pointerleave

  • [桌面] mouseleave

返回

Promise<Event[]>

leave("button"); // Moves out of <button>
press(keyStrokes[, options])

对当前*活动元素*执行键盘事件序列。

事件顺序如下:

  • keydown

  • keyup

返回

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 会模拟许多低级功能:clipboardfetchlocalStorage 等。这些模拟旨在不产生任何会干扰测试运行程序或其他测试上下文的副作用,同时仍然提供相同的接口以允许测试无缝依赖这些功能。

还需要(大多数时候)对这些功能强制执行操作或更改其行为以进行测试,因此存在与这些模拟功能交互的帮助程序。以下部分将列出主要的模拟功能以及与它们交互的方法。

时间

大多数异步功能都被嘲笑:“计时器”(setTimeoutsetIntervalrequestAnimationFrame)、Dateperformance 都表现正常,但可以手动取消或加速,以大大缩短测试的实际持续时间。例如:所有“计时器”在每次测试结束时都被取消,以避免对下一次测试产生副作用。

重要

有 2 个主要的计时行为*不*被嘲笑:

  • Promise 对象及相关API;

  • OWL 的计时器函数:要等待 OWL 渲染函数,您必须求助于 animationFrame 帮助程序。

网络

一般来说,我们不想在测试中执行实际的网络调用。为了确保这一点,对 fetchXMLHttpRequest 的所有调用都已重新路由到指定给 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])

激活模拟 WorkerSharedWorker 类:

  • 然后,由工作 URL 获取的实际代码将由 window.fetch 处理(请参阅:js:meth:mockFetch);

  • 创建工作线程后将调用 onWorkerConnected 回调。

显着的全球特征

以下功能可能没有添加任何特定的模拟功能,但它们确实按预期工作,而不会更改它们的实际属性:

  • Document

    titlecookie 都可以设置和读取,而不改变当前文档的实际属性。

  • History

    history API 被模拟并绑定到 mockLocation 对象以返回相同的值并提供一致性。

  • Location

    Hoot 返回一个 mockLocation 对象来代替 window.location 使用,但这依赖于实际生产代码中间接的使用。

    重要

    仅当在生产代码和调用 window.location 之间设置间接时,此功能才有效。在 Odoo 中,它之所以有效,是因为 @web/core/browser 模块提供了这样的间接寻址,并且该模块在测试环境中被模拟以重定向到 mockLocation 对象。

  • Navigator

    最常用的导航器功能(例如 clipboard API 和 userAgent)已被嘲笑以劫持其实际行为。它的 permissions 对象已绑定到权限 API 的全局模拟。

  • Notification

    通知已被模拟,“通知”权限绑定到全局模拟权限 API。

  • Permissions

    权限可以通过赋予 "granted""denied" 状态来启用或禁用其他 API。这可以通过 mockPermission 帮助程序来完成。

  • Storage

    localStoragesessionStorage 都指向“虚拟”存储。

  • Touch

    对于给定的测试/套件,可以使用 mockTouch() 帮助程序全局强制激活或停用触摸功能。它将模拟窗口上 ontouchstart 等触摸处理程序的存在,以及设置为 finecoarse"pointer" 媒体。

相关帮手

mockPermission(name[, value])

设置给定权限的给定值。这允许启用或阻止某些 API(请参阅 Permissions API)。

// Prevents the whole notification API from working
mockPermission("notifications", "denied");
mockTouch(setTouch)

在当前 Window 中打开或关闭触摸功能。