HOOT¶
نمای کلی¶
HOOT یک چارچوب تست است که با Owl نوشته شده و ویژگیهای کلیدی آن عبارتند از:
ثبت و اجرای تستها و test suiteها؛
نمایش رابطی شهودی برای مشاهده و فیلتر کردن نتایج تست؛
ارائهٔ راههایی برای تعامل با DOM بهمنظور شبیهسازی اقدامات کاربر؛
ارائهٔ کمککنندههای سطح پایین که اجازهٔ mock کردن اشیای سراسری مختلف را میدهند.
از این رو، بهعنوان یک lib/ در کدبیس Odoo ادغام شده و دو ماژول اصلی را صادر میکند:
@odoo/hoot-dom: (قابل استفاده در tourها) کمککنندههایی برای:جستوجوی عناصر از DOM، مانند
queryAll()وwaitFor()؛
@odoo/hoot: (فقط در تستهای واحد استفاده میشود) تمام ویژگیهای چارچوب تست:test،describeوexpectهوکهای تست مانند
afterوafterEachمدیریت fixture با
getFixtureمدیریت تاریخ و زمان مانند
mockDateیاadvanceTimemock کردن پاسخهای شبکه از طریق
mockFetch()یاmockWebSocket()هر helper که توسط
@odoo/hoot-domصادر شده است
توجه
این بخش از مستندات قرار نیست تمام helperهای موجود در Hoot را فهرست کند (فهرست کامل را میتوان در خود ماژول @odoo/hoot یافت). هدف در اینجا نشان دادن پرکاربردترین helperها و توجیه برخی از تصمیماتی است که به شکل فعلی چارچوب تست منجر شدهاند.
Running tests¶
در Odoo، تستهای واحد frontend را میتوان با رفتن به URL /web/tests اجرا کرد. بیشتر تنظیمات لازم برای فراخوانی test runner از پیش انجام شده است:
bundle
web.assets_unit_testsاز پیش تعریف شده است و همهٔ تستهای تعریفشده در بیشتر افزونهها را شناسایی میکند؛فایل
start.hoot.jsمسئول فراخوانی test runner با تابع نقطهٔ ورود صادرشدهٔstartآن است.
هنگام رفتن به صفحهٔ تست، تستها بهصورت ترتیبی اجرا شده و نتایج در کنسول و در GUI نمایش داده میشوند (در صورتی که در حالت headless اجرا نشود).
گزینههای Runner¶
runner را میتوان به یکی از روشهای زیر پیکربندی کرد:
از طریق رابط کاربری (با منوی کشویی پیکربندی و نوار جستجو)؛
یا از طریق پارامترهای query در URL (مثلاً
?headlessبرای اجرا در حالت headless).
فهرست گزینههای موجود برای runner در ادامه آمده است:
bailتعداد تستهای ناموفقی که پس از آن test runner متوقف میشود. مقدار falsy (شامل 0) یعنی runner هرگز متوقف نشود. (پیشفرض:
0)
debugTestمشابه فیلتر
FILTER_SCHEMA.test، اما test runner را نیز در حالت «debug» قرار میدهد. برای اطلاعات بیشتر بهTestRunner.debugمراجعه کنید. (پیشفرض:false)
fpsمقدار frames per seconds را تنظیم میکند (به میلیثانیه تبدیل شده و در
advanceFrameاستفاده میشود)
filterرشتهٔ جستجو که تستها/suiteهای مطابق را بر اساس نام کامل آنها (شامل suite(های) والد) و برچسبهایشان فیلتر میکند. (پیشفرض:
"")
frameRateمقدار تخمینی فریمهای رندرشده در ثانیه، که هنگام mock کردن فریمهای انیمیشن استفاده میشود. (پیشفرض:
60fps)
funحالوهوا را شاد میکند. (پیشفرض:
false)
headlessآیا رابط کاربری test runner رندر شود یا خیر. (پیشفرض:
false)
idشناسههای suiteها یا تستهایی که بهصورت انحصاری اجرا میشوند. شناسهٔ یک job بهصورت قطعی بر اساس نام کامل آن تولید میشود.
loglevelسطح log مورد استفادهٔ test runner. هرچه سطح بالاتر باشد، logهای بیشتری نمایش داده میشود:
0: فقط logهای runner نمایش داده میشود (پیشفرض)1: نتایج همهٔ suiteها نیز ثبت میشود2: نتایج همهٔ تستها نیز ثبت میشود3: اطلاعات debug برای هر تست نیز ثبت میشود
manualاینکه آیا test runner باید پس از بارگذاری صفحه بهصورت دستی شروع شود (پیشفرض شروع خودکار است). (پیشفرض:
false)
notrycatchایمنی دستورات
try .. catchپیرامون تابع اجرای هر تست را حذف میکند تا اجازه دهد خطاها به مرورگر منتقل شوند. (پیشفرض:false)
orderترتیب اجرای تستها را تعیین میکند:
"fifo": تستها بهصورت ترتیبی همانطور که در سیستم فایل اعلام شدهاند اجرا میشوند؛"lifo": تستها بهصورت ترتیبی به ترتیب معکوس اجرا میشوند؛"random": تستها و suiteها در داخل suite والد خود بهصورت تصادفی مرتب میشوند.
presetمحیطی که test runner در آن اجرا میشود. این پارامتر برای تعیین مقدار پیشفرض سایر ویژگیها استفاده میشود، یعنی:
user agent؛
پشتیبانی از لمس؛
اندازهٔ موردانتظار viewport.
showdetailنحوهٔ نمایش جزئیات تستهای ناموفق در رابط کاربری را تعیین میکند. (پیشفرض:
"first-fail")
tagنام برچسبهای تستها و suiteهایی که بهصورت انحصاری اجرا میشوند (غیرحساس به حروف). (پیشفرض: خالی)
timeoutمدتزمانی (به میلیثانیه) که در پایان آن، یک تست بهصورت خودکار شکست میخورد. (پیشفرض:
5ثانیه)
توجه
هنگام انتخاب تستها و suiteها برای اجرا، یک OR ضمنی بین فیلترهای شاملکننده اعمال میشود. این بدان معنی است که افزودن فیلترهای شاملکنندهٔ بیشتر منجر به اجرای تستهای بیشتر میشود. این مورد برای فیلترهای filter، id و tag صدق میکند (در عوض فیلترهای حذفکننده تستهای مطابق را از فهرست تستهای قابل اجرا حذف میکنند).
نوشتن تستها¶
آزمایش¶
نوشتن یک تست میتواند بسیار ساده باشد، چرا که صرفاً به فراخوانی تابع test با یک نام و یک تابع که حاوی منطق تست است نیاز دارد.
در اینجا یک مثال ساده آورده شده است:
import { expect, test } from "@odoo/hoot";
test("My first test", () => {
expect(2 + 2).toBe(4);
});
Describe¶
بیشتر اوقات، تستها به این سادگی نیستند. اغلب نیاز به مقداری setup و teardown دارند و گاهی لازم است در قالب یک suite گروهبندی شوند. اینجاست که تابع describe وارد عمل میشود.
به این صورت میتوانید یک suite و یک تست درون آن را اعلام کنید:
import { describe, expect, test } from "@odoo/hoot";
describe("My first suite", () => {
test("My first test", () => {
expect(2 + 2).toBe(4);
});
});
مهم
در Odoo، تمام فایلهای تست در یک محیط ایزوله اجرا میشوند و درون یک بلوک describe سراسری بستهبندی میگردند (نام suite برابر با مسیر فایل تست است).
با در نظر گرفتن این موضوع، نیازی به اعلام یک suite در فایلهای تست خود ندارید، هرچند همچنان میتوانید در همان فایل sub-suiteها را اعلام کنید، اگر میخواهید برای اهداف سازماندهی یا برچسبگذاری suite فایل را تقسیم کنید.
Expect¶
تابع expect تابع assertion اصلی این چارچوب است. برای assertion این مورد استفاده میشود که یک مقدار یا شیء همان چیزی است که انتظار میرود یا در وضعیتی است که قرار است باشد. برای این کار، چند modifier و طیف وسیعی از matcherها فراهم میکند.
Modifierها¶
یک modifier از expect یک getter است که مجموعهٔ دیگری از matcherهای تغییریافته را که بهصورت خاصی رفتار میکنند، برمیگرداند.
notنتیجهٔ matcher بعدی را معکوس میکند: در صورتی موفق میشود که matcher شکست بخورد.
expect(true).not.toBe(false);
resolvesمنتظر میماند تا مقدار (
Promise) «resolve» شود سپس matcher بعدی را با مقدار resolveشده اجرا میکند.await expect(Promise.resolve(42)).resolves.toBe(42);
rejectsمنتظر میماند تا مقدار (
Promise) «reject» شود سپس matcher بعدی را با دلیل reject اجرا میکند.await expect(Promise.reject("error")).rejects.toBe("error");
توجه
modifierهای resolves و rejects تنها زمانی در دسترس هستند که مقدار یک promise باشد و یک promise بازمیگردانند که پس از انجام assertion، resolve میشود.
matcherهای معمولی¶
matcherها تعیین میکنند که با مقدار در حال تست چه کاری انجام شود. برخی آن مقدار را همانگونه که هست میگیرند، در حالی که برخی دیگر آن مقدار را پیش از انجام assertion روی آن تبدیل میکنند (مثلاً matcherهای DOM).
توجه داشته باشید که پارامتر آرگومان آخر تمام matcherها یک دیکشنری اختیاری با گزینههای اضافی است که در آن میتوان یک message سفارشی برای assertion ارائه داد تا کانتکست/خاصبودگی بیشتری اضافه شود.
فهرست اول matcherها بر پایهٔ primitive یا object هستند و رایجترین موارد محسوب میشوند:
- 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: بدون محتوا (یعنی بدون مقدار یا متن)هر چیز دیگر: مقدار falsy (
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با شکل دادهشده را شامل شود.مقدار دریافتشده میتواند یک iterable یا یک object باشد (در صورت object بودن،
itemباید یک کلید یا یک tuple باشد که یک ورودی در آن object را نشان میدهد).توجه داشته باشید که این یک مقایسهٔ سختگیرانه نیست: آیتم برای برابری عمیق در برابر هر آیتم از iterable مطابقت داده میشود.
پارامترها
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");
matcherهای DOM¶
فهرست بعدی matcherها بر پایهٔ node هستند و برای assertion وضعیت یک node یا فهرستی از nodeها استفاده میشوند. آنها معمولاً یک سلکتور سفارشی را بهعنوان آرگومان تابع expect میگیرند (هرچند یک Node یا iterable از Node نیز پذیرفته میشود).
- toBeChecked([options])¶
انتظار دارد
Targetدریافتشده"checked"باشد یا اگر گزینهٔ همنام رویtrueتنظیم شده باشد،"indeterminate"باشد.پارامترها
options:{ message?: string, indeterminate?: boolean }
مثالها
expect("input[type=checkbox]").toBeChecked();
- toBeDisplayed([options])¶
انتظار دارد
Targetدریافتشده «displayed» باشد، به این معنی که:یک bounding box دارد؛
در document ریشه قرار دارد.
پارامترها
options:{ message?: string }
مثالها
expect(document.body).toBeDisplayed(); expect(document.createElement("div")).not.toBeDisplayed();
- toBeEnabled([options])¶
انتظار دارد
Targetدریافتشده «enabled» باشد، به این معنی که با pseudo-selector:enabledمطابقت دارد.پارامترها
options:{ message?: string }
مثالها
expect("button").toBeEnabled(); expect("input[type=radio]").not.toBeEnabled();
- toBeFocused([options])¶
انتظار دارد
Targetدریافتشده در document مالک خود «focused» باشد.پارامترها
options:{ message?: string }
- toBeVisible([options])¶
انتظار دارد
Targetدریافتشده «visible» باشد، به این معنی که:یک bounding box دارد؛
در document ریشه قرار دارد؛
توسط ویژگیهای 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])¶
انتظار دارد
innerHTMLمربوط بهTargetدریافتشده با مقدارexpectedمطابقت داشته باشد (پس از قالببندی).پارامترها
expected:string | RegExpoptions:{ message?: string, type?: "html" | "xml", tabSize?: number, keepInlineTextNodes?: boolean }
مثالها
expect(".my_element").toHaveInnerHTML(` Some <strong>text</strong> `);
- toHaveOuterHTML(expected[, options])¶
انتظار دارد
outerHTMLمربوط بهTargetدریافتشده با مقدار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دادهشده مطابق داشته باشد. اگر هیچ مقداری داده نشود: matcher در عوض بررسی میکند که خاصیت دادهشده روی target وجود دارد.پارامترها
property:stringvalue:anyoptions:{ message?: string }
مثالها
expect("button").toHaveProperty("tabIndex", 0); expect("input").toHaveProperty("ontouchstart"); expect("script").toHaveProperty("src", "./index.js");
- toHaveRect(rect[, options])¶
انتظار دارد
DOMRectمربوط بهTargetدریافتشده با شیءrectدادهشده مطابقت داشته باشد. شیءrectمیتواند یکی از موارد زیر باشد:یک شیء
DOMRect؛یک رشتهٔ سلکتور CSS (برای دریافت rect تنها عنصر مطابق)؛
یک node.
اگر مقدار حاصل
rectیک node باشد، rectهای هر دو node با هم مقایسه میشوند.پارامترها
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 دادهشده مطابقت داشته باشد.پارامترها
style:string | Record<string, string | RegExp>options:{ message?: string }
مثالها
expect("button").toHaveStyle({ color: "red" }); expect("p").toHaveStyle("text-align: center");
- toHaveText(text[, options])¶
انتظار دارد محتوای
textمربوط بهTargetدریافتشده یکی از موارد زیر باشد:دقیقاً برابر با یک رشتهٔ دادهشده باشد؛
با یک عبارت منظم دادهشده مطابقت داشته باشد.
توجه: از
innerHTMLبرای بازیابی محتوای متنی استفاده میشود تا visibility مربوط به 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])¶
Expects the value of the received
Targetto either:be strictly equal to a given string or number;
match a given regular expression;
contain file objects matching the given
fileslist.
پارامترها
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"]);
Static methods¶
The expect helper function also contains static methods that can be used to run
through a detached testing flow that isn't bound to one specific value at a certain
moment.
These methods are mainly used to register steps or errors in the scope of the current test, and to evaluate them later on.
- expect.assertions(expected)¶
- نشانوندها
expected (
number()) --
Expects the current test to have the
expectedamount of assertions. This number cannot be less than 1.توجه
It is generally preferred to use
expect.step()andexpect.verifySteps()instead as it is more reliable and allows to test more extensively.
- expect.errors(expected)¶
- نشانوندها
expected (
number()) --
Expects the current test to have the
expectedamount of errors.This also means that from the moment this function is called, the test will accept that amount of errors before being considered as failed.
- expect.step(value)¶
- نشانوندها
value (
unknown()) --
Registers a step for the current test, that can be consumed by
expect.verifySteps(). Unconsumed steps will fail the test.
- expect.verifyErrors(errors[, options])¶
- نشانوندها
errors (
unknown[]()) --options (
{ message?: string }()) --
- بازگشت ها
boolean
Expects the received matchers to match the errors thrown since the start of the test or the last call to
expect.verifyErrors(). Calling this matcher will reset the list of current errors.expect.verifyErrors([/RPCError/, /Invalid domain AST/]);
- expect.verifySteps(steps[, options])¶
- نشانوندها
steps (
unknown[]()) --options (
{ ignoreOrder?: boolean, message?: string, partial?: boolean }()) --
- بازگشت ها
boolean
Expects the received steps to be equal to the steps emitted since the start of the test or the last call to
expect.verifySteps(). Calling this matcher will reset the list of current steps.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>
Same as
expect.verifyErrors(), but will not immediatly fail if errors are not caught yet, and will instead wait for a certain timeout (default: 2000ms) to allow errors to be caught later.Checks are performed initially, at the end of the timeout, and each time an error is detected.
fetch("invalid/url"); await expect.waitForErrors([/RPCError/]);
- expect.waitForSteps(steps[, options])¶
- نشانوندها
steps (
unknown[]()) --options (
{ ignoreOrder?: boolean, message?: string, partial?: boolean }()) --
- بازگشت ها
Promise<boolean>
Same as
expect.verifySteps(), but will not immediatly fail if steps have not been registered yet, and will instead wait for a certain timeout (default: 2000ms) to allow steps to be registered later.Checks are performed initially, at the end of the timeout, and each time a step is registered.
// ... step on each 'web_read_group' call fetch(".../call_kw/web_read_group"); await expect.waitForSteps(["web_read_group"]);
DOM: queries¶
Custom DOM selectors¶
Here's a brief section on DOM selectors in Hoot, as they support additional pseudo-classes that can be used to target elements based on non-standard features, such as their text content or their global position in the document.
:contains(text)matches nodes whose text content matches the given
textgiven text supports regular expression syntax (e.g.
:contains(/^foo.+/)) and is case-insensitive (unless using theiflag at the end of the regex)
:displayedmatches nodes that are "displayed" (see
isDisplayed)
:emptymatches nodes that have an empty content (value or text content)
:eq(n)returns the nth node based on its global position (0-based index);
:firstreturns the first node matching the selector (in the whole document)
:focusablematches nodes that can be "focused" (see
isFocusable)
:hiddenmatches nodes that are not "visible" (see
isVisible)
:iframematches nodes that are
<iframe>elements, and returns theirbodyif it is ready
:lastreturns the last node matching the selector (in the whole document)
:selectedmatches nodes that are selected (e.g.
<option>elements)
:shadowmatches nodes that have shadow roots, and returns their shadow root
:scrollablematches nodes that are scrollable (see
isScrollable)
:value(text)matches nodes whose value matches the given
textgiven text supports regular expression syntax (e.g.
:value(/^foo.+/)) and is case-insensitive (unless using theiflag at the end of the regex)
:visiblematches nodes that are "visible" (see
isVisible)
Query & node properties helpers¶
Hoot provides helpers to query nodes and some of their properties in a streamlined
and elegant way. This can mainly be done through the use of queryX helpers:
- queryAll(target[, options])¶
Returns a list of nodes matching the given
Target. This function can either be used as a template literal tag (only supports string selector without options) or invoked the usual way.The target can be:
a
Node(or an iterable of nodes), orWindowobject;a
Documentobject (which will be converted to its body);a string representing a custom selector (which will be queried from the
rootoption).
An
optionsobject can be specified to filter 1 the results:count: the exact number of nodes to match (throws an error if the number of nodes doesn't match);displayed: whether the nodes must be "displayed" (seeisDisplayed);focusable: whether the nodes must be "focusable" (seeisFocusable);root: the root node to query the selector in (defaults to the current fixture);visible: whether the nodes must be "visible" (seeisVisible). * This option impliesdisplayed
- 1
these filters (except for
countandroot) achieve the same result as using their homonym pseudo-classes on the final group of the given selector string, e.g.:// These 2 will return the same result queryAll`ul > li:visible`; queryAll("ul > li", { visible: true });
- بازگشت ها
Node[]
- queryAllAttributes(target, attribute[, options])¶
Performs a
queryAll()on the giventargetand returns a list of attribute values.- بازگشت ها
string[]list of attribute values
- queryAllProperties(target, property[, options])¶
Performs a
queryAll()on the giventargetand returns a list of property values.- بازگشت ها
unknown[]list of property values
- queryAllTexts(target[, options])¶
Performs a
queryAll()on the giventargetand returns a list of text contents.- بازگشت ها
string[]list of text contents
- queryAllValues(target[, options])¶
Performs a
queryAll()on the giventargetand returns a list of values.- بازگشت ها
string[]a list of values
- queryAttribute(target, attribute[, options])¶
Performs a
queryOne()with the given arguments and returns the value of the givenattributeof the matching node.- بازگشت ها
stringthe attribute value
- queryFirst(target[, options])¶
Performs a
queryAll()with the given arguments and returns the first result ornull.- بازگشت ها
Node|nullthe first matching node
- queryOne(target[, options])¶
Performs a
queryAll()with the given arguments, along with a forcedcount: 1option to ensure only one node matches the givenTarget.The returned value is a single node instead of a list of nodes.
- بازگشت ها
Nodea single node
- queryText(target[, options])¶
Performs a
queryOne()with the given arguments and returns the text of the matching node.- بازگشت ها
stringtext of the matching node
- queryValue(target[, options])¶
Performs a
queryOne()with the given arguments and returns the value of the matching node.- بازگشت ها
stringvalue of the matching node
All of the above helpers are synchronous, meaning that they will attempt to query nodes instantly. Although some use cases require the element to be awaited for an arbitrary amount of time, unknown in advance due to UI fetching and rendering complexity.
Hoot provides 2 methods to wait for an element to appear / disappear within a certain
time frame (by default: 200 milliseconds) for such cases:
- waitFor(target[, options])¶
Combination of
queryAll()andwaitUntil(): waits for a given target to match elements in the DOM and returns the first matching node when it appears (or immediately if it is already present).- بازگشت ها
Promise<Node>containing the first matching node
DOM: interaction helpers¶
Along with querying elements, it is often required to interact with them. As such, Hoot provides helpers to simulate various user interactions on elements.
These can be split into 2 types based on their parameters: pointer-based interaction helpers, and the other ones.
Pointer interaction helpers:¶
Pointer interaction helpers (such as click() or drag()) will simulate
actual pointer movements and events on the given target, and on any previous element
the pointer was supposed to have been.
- check(target[, options])¶
Ensures that the given
Targetis checked.If it is not checked, a
click()is simulated on the input. If the input is still not checked after the click, an error is thrown.- بازگشت ها
Promise<Event[]>
check("input[type=checkbox]"); // Checks the first <input> checkbox element
- click(target[, options])¶
Performs a click sequence on the given
Target.The event sequence is as follows:
pointerdown[desktop]
mousedown[touch]
touchstart[target is not active element]
blur[target is focusable]
focuspointerup[desktop]
mouseup[touch]
touchendclickdblclickif click is not prevented & current click count is even
- بازگشت ها
Promise<Event[]>
click("button"); // Clicks on the first <button> element
- dblclick(target[, options])¶
Performs two
click()sequences on the givenTarget.- بازگشت ها
Promise<Event[]>
dblclick("button"); // Double-clicks on the first <button> element
- drag(target[, options])¶
Starts a drag sequence on the given
Target.Returns a set of helper functions to direct the sequence:
moveTo: moves the pointer to the given target;drop: drops the dragged element on the given target (if any);cancel: cancels the drag sequence.
- بازگشت ها
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])¶
Performs a hover sequence on the given
Target.The event sequence is as follows:
pointerover[desktop]
mouseoverpointerenter[desktop]
mouseenterpointermove[desktop]
mousemove[touch]
touchmove
- بازگشت ها
Promise<Event[]>
hover("button"); // Hovers the first <button> element
- pointerDown(target[, options])¶
Performs a pointer down on the given
Target.The event sequence is as follows:
pointerdown[desktop]
mousedown[touch]
touchstart[target is not active element]
blur[target is focusable]
focus
- بازگشت ها
Promise<Event[]>
pointerDown("button"); // Focuses to the first <button> element
- pointerUp(target[, options])¶
Performs a pointer up on the given
Target.The event sequence is as follows:
pointerup[desktop]
mouseup[touch]
touchend
- بازگشت ها
Promise<Event[]>
pointerUp("body"); // Triggers a pointer up on the <body> element
- scroll(target, position[, options])¶
Performs a scroll event sequence on the given
Target.The event sequence is as follows:
[desktop]
wheelscroll
- بازگشت ها
Promise<Event[]>
scroll("body", { y: 0 }); // Scrolls to the top of <body>
- setInputRange(target, value[, options])¶
Sets the given value to the current "input[type=range]"
Target.The event sequence is as follows:
pointerdowninputchangepointerup
- بازگشت ها
Promise<Event[]>
- uncheck(target[, options])¶
Ensures that the given
Targetis unchecked.If it is checked, a
click()is triggered on the input. If the input is still checked after the click, an error is thrown.- بازگشت ها
Promise<Event[]>
uncheck("input[type=checkbox]"); // Unchecks the first <input> checkbox element
Other interaction helpers:¶
Other interaction helpers will not have a target parameter. It is not needed,
since pressing keys on a keyboard (for example) is done on the current active element.
- clear([options])¶
Clears the value of the current active element.
This is done using the following sequence:
pressing
"Control"&"A"to select the whole value;pressing
"Backspace"to delete the value;(optional) triggering a
"change"event by pressing"Enter".
- بازگشت ها
Promise<Event[]>
clear(); // Clears the value of the current active element
- edit(value[, options])¶
Combination of
clear()andfill():first, clears the input value (if any)
then fills the input with the given value
- بازگشت ها
Promise<Event[]>
fill("foo"); // Types "foo" in the active element edit("Hello World"); // Replaces "foo" by "Hello World"
- fill(value[, options])¶
Fills the current active element with the given
value. This helper is intended for<input>and<textarea>elements, with the exception of"checkbox"and"radio"types, which should be selected using thecheckhelper.If the target is an editable input, its string
valuewill be input one character at a time, each generating its corresponding keyboard event sequence. This behavior can be overridden by passing theinstantlyoption, which will instead simulate acontrol+vkeyboard sequence, resulting in the whole text being pasted.Note that the given value is appended to the current value of the element.
If the active element is a
<input type="file"/>, thevalueshould be aFile/list ofFileobject(s).- بازگشت ها
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])¶
Performs a key down sequence on the current active element.
The event sequence is as follows:
keydown
Additional actions will be performed depending on the key pressed:
Tab: focus next (or previous withshift) focusable element;c: copy current selection to clipboard;v: paste current clipboard content to current element;Enter: submit the form if the target is a<button type="button">or a<form>element, or trigger achangeevent on the target if it is an<input>element;Space: trigger aclickevent on the target if it is an<input type="checkbox">element.
- بازگشت ها
Promise<Event[]>
keyDown(" "); // Space key
- keyUp(keyStrokes[, options])¶
Performs a key up sequence on the current active element.
The event sequence is as follows:
keyup
- بازگشت ها
Promise<Event[]>
keyUp("Enter");
- leave([options])¶
Performs a leave sequence on the current
Window.The event sequence is as follows:
pointermove[desktop]
mousemove[touch]
touchmovepointerout[desktop]
mouseoutpointerleave[desktop]
mouseleave
- بازگشت ها
Promise<Event[]>
leave("button"); // Moves out of <button>
- press(keyStrokes[, options])¶
Performs a keyboard event sequence on the current active element.
The event sequence is as follows:
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]])¶
Performs a resize event sequence on the current
Window.The event sequence is as follows:
resize
The target will be resized to the given dimensions, enforced by
!importantstyle attributes.- بازگشت ها
Promise<Event[]>
resize("body", { width: 1000, height: 500 }); // Resizes <body> to 1000x500
- select(value[, options])¶
Performs a selection event sequence on the current active element. This helper is intended for
<select>elements only.The event sequence is as follows:
change
- بازگشت ها
Promise<Event[]>
click("select[name=country]"); // Focuses <select> element select("belgium"); // Selects the <option value="belgium"> element
- setInputFiles(files[, options])¶
Gives the given
Filelist to the current file input. This helper only works if a file input has been previously interacted with (by clicking on it).- بازگشت ها
Promise<Event[]>
- unload([options])¶
Triggers a "beforeunload" event on the current
Window.- بازگشت ها
Promise<Event[]>
Mockها¶
By default, a lot of low-level features are mocked by Hoot: clipboard, fetch, localStorage,
etc. These mocks are intended to not produce any side-effect that would disturb the test runner
or the context of other tests, while still providing the same interface to allow tests to rely
on these features seamlessly.
There is also a need (most of the time) to force actions on these features or change their behavior for a test, so there exist helpers to interact with these mocked features. The following sections will list the main mocked features and the means to interact with them.
Time¶
Most asynchronous features are mocked: "timers" (setTimeout, setInterval and
requestAnimationFrame), Date and performance all behave normally, but can be canceled or
sped-up manually to considerably shorten the actual duration of tests. For example: all "timers"
are canceled at the end of each test to avoid side-effects for the next one.
مهم
There are 2 main timing behaviors that are NOT mocked:
Promiseobjects and related API;OWL's timer functions: to wait for OWL rendering functions, you'll have to resort to the
animationFramehelper.
Network¶
In general, we don't want to perform actual network calls in tests. To ensure this, all calls
to fetch and XMLHttpRequest have been re-routed to a function given to
mockFetch().
توجه
In Odoo, this is generally implicitly handled by a MockServer which is spawned by the mock environment, i.e. any time a component is rendered using the mountWithCleanup helper.
Related helpers¶
- mockFetch([fetchFn])¶
Mocks the fetch function by replacing it with a given
fetchFn.The return value of
fetchFnis used as the response of the mocked fetch, or wrapped in aMockResponseobject if it does not meet the required format.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])¶
Activates mock WebSocket classe:
websocket connections will be handled by
window.fetch(seemockFetch());the
onWebSocketConnectedcallback will be called after a websocket has been created.
- mockWorker([onWorkerConnected])¶
Activates mock
WorkerandSharedWorkerclasses:actual code fetched by worker URLs will then be handled by
window.fetch(seemockFetch());the
onWorkerConnectedcallback will be called after a worker has been created.
Notable global features¶
The following features may not have any specific mocked feature added, but they do work as expected without changing the actual properties they were meant to:
-
Both
titleandcookiecan be set and read without changing the actual properties of the current document. -
The
historyAPI is mocked and bound to themockLocationobject to return the same values and provide consistency. -
Hoot returns a
mockLocationobject to use instead ofwindow.location, but this relies on the use of an indirection in the actual production code.مهم
This feature will only work if an indirection is set between production code and calls to
window.location. In Odoo, it works because the@web/core/browsermodule provides such an indirection, and that module is mocked in test environments to redirect to themockLocationobject. -
Most used navigator features, such as the
clipboardAPI anduserAgent, have been mocked to hijack their actual behaviors. Itspermissionsobject has been bound to a global mock of the permissions API. -
Notifications have been mocked, with the "notification" permissions bound to the global mocked permissions API.
-
Permissions can enable or disable other APIs by being given the
"granted"or"denied"statuses. This can be done through themockPermissionhelper. -
localStorageandsessionStorageboth point to "virtual" storages. -
Touch features can be force-activated or deactivated globally for a given test/suite using the
mockTouch()helper. It will mock both the presence of touch handlers likeontouchstarton window, as well as the"pointer"media being set tofineorcoarse.
Related helpers¶
- mockPermission(name[, value])¶
Sets the given value for the given permission. This allows to enable or prevent certain APIs (see the Permissions API).
// Prevents the whole notification API from working mockPermission("notifications", "denied");
- mockTouch(setTouch)¶
Toggles touch features on or off in the current
Window.