@razohq/razo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/dist/chunk-UCLP2ILU.mjs +20 -0
- package/dist/chunk-UCLP2ILU.mjs.map +1 -0
- package/dist/events-D_lCLx1V.d.mts +44 -0
- package/dist/events-D_lCLx1V.d.ts +44 -0
- package/dist/index.d.mts +336 -0
- package/dist/index.d.ts +336 -0
- package/dist/index.js +961 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +892 -0
- package/dist/index.mjs.map +1 -0
- package/dist/reporter.d.mts +31 -0
- package/dist/reporter.d.ts +31 -0
- package/dist/reporter.js +91 -0
- package/dist/reporter.js.map +1 -0
- package/dist/reporter.mjs +50 -0
- package/dist/reporter.mjs.map +1 -0
- package/dist/vite.d.mts +29 -0
- package/dist/vite.d.ts +29 -0
- package/dist/vite.js +55 -0
- package/dist/vite.js.map +1 -0
- package/dist/vite.mjs +26 -0
- package/dist/vite.mjs.map +1 -0
- package/package.json +110 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,892 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AI_STEP_ATTACHMENT,
|
|
3
|
+
emitStepEvent,
|
|
4
|
+
stripAnsi
|
|
5
|
+
} from "./chunk-UCLP2ILU.mjs";
|
|
6
|
+
|
|
7
|
+
// src/controls/Control.ts
|
|
8
|
+
import { expect, test } from "@playwright/test";
|
|
9
|
+
var SENTENCES = {
|
|
10
|
+
click: (c) => `Click ${c.controlType} "${c.name}"`,
|
|
11
|
+
fill: (c) => `Type "${c.detail}" into ${c.controlType} "${c.name}"`,
|
|
12
|
+
clear: (c) => `Clear ${c.controlType} "${c.name}"`,
|
|
13
|
+
select: (c) => `Select option "${c.detail}" in ${c.controlType} "${c.name}"`,
|
|
14
|
+
check: (c) => `Check ${c.controlType} "${c.name}"`,
|
|
15
|
+
uncheck: (c) => `Uncheck ${c.controlType} "${c.name}"`,
|
|
16
|
+
choose: (c) => `Choose "${c.detail}" in ${c.controlType} "${c.name}"`,
|
|
17
|
+
"choose-many": (c) => `Choose options "${c.detail}" in ${c.controlType} "${c.name}"`,
|
|
18
|
+
open: (c) => `Open ${c.controlType} "${c.name}"`,
|
|
19
|
+
attach: (c) => `Attach "${c.detail}" to ${c.controlType} "${c.name}"`,
|
|
20
|
+
set: (c) => `Set ${c.controlType} "${c.name}" to "${c.detail}"`,
|
|
21
|
+
"assert-visible": (c) => `Assert ${c.controlType} "${c.name}" is visible`,
|
|
22
|
+
"assert-enabled": (c) => `Assert ${c.controlType} "${c.name}" is enabled`,
|
|
23
|
+
"assert-disabled": (c) => `Assert ${c.controlType} "${c.name}" is disabled`,
|
|
24
|
+
"assert-text": (c) => `Assert ${c.controlType} "${c.name}" has text "${c.expected}"`,
|
|
25
|
+
"assert-checked": (c) => `Assert ${c.controlType} "${c.name}" is checked`,
|
|
26
|
+
"assert-unchecked": (c) => `Assert ${c.controlType} "${c.name}" is not checked`,
|
|
27
|
+
"assert-selected": (c) => `Assert ${c.controlType} "${c.name}" has option "${c.expected}" selected`,
|
|
28
|
+
"assert-value": (c) => `Assert ${c.controlType} "${c.name}" has value "${c.expected}"`,
|
|
29
|
+
"assert-href": (c) => `Assert ${c.controlType} "${c.name}" points to "${c.expected}"`,
|
|
30
|
+
// Universal interactions
|
|
31
|
+
hover: (c) => `Hover over ${c.controlType} "${c.name}"`,
|
|
32
|
+
"double-click": (c) => `Double-click ${c.controlType} "${c.name}"`,
|
|
33
|
+
"right-click": (c) => `Right-click ${c.controlType} "${c.name}"`,
|
|
34
|
+
press: (c) => `Press "${c.detail}" on ${c.controlType} "${c.name}"`,
|
|
35
|
+
focus: (c) => `Focus ${c.controlType} "${c.name}"`,
|
|
36
|
+
"scroll-to": (c) => `Scroll to ${c.controlType} "${c.name}"`,
|
|
37
|
+
// detail carries the drop target already rendered as `type "name"`
|
|
38
|
+
drag: (c) => `Drag ${c.controlType} "${c.name}" onto ${c.detail}`,
|
|
39
|
+
// Universal assertions
|
|
40
|
+
"assert-hidden": (c) => `Assert ${c.controlType} "${c.name}" is hidden`,
|
|
41
|
+
"assert-focused": (c) => `Assert ${c.controlType} "${c.name}" is focused`,
|
|
42
|
+
"assert-contains-text": (c) => `Assert ${c.controlType} "${c.name}" contains text "${c.expected}"`,
|
|
43
|
+
"assert-attribute": (c) => `Assert ${c.controlType} "${c.name}" attribute "${c.detail}" is "${c.expected}"`,
|
|
44
|
+
"assert-empty": (c) => `Assert ${c.controlType} "${c.name}" is empty`,
|
|
45
|
+
// Switch
|
|
46
|
+
"turn-on": (c) => `Turn on ${c.controlType} "${c.name}"`,
|
|
47
|
+
"turn-off": (c) => `Turn off ${c.controlType} "${c.name}"`,
|
|
48
|
+
"assert-on": (c) => `Assert ${c.controlType} "${c.name}" is on`,
|
|
49
|
+
"assert-off": (c) => `Assert ${c.controlType} "${c.name}" is off`,
|
|
50
|
+
// Combobox
|
|
51
|
+
pick: (c) => `Pick "${c.detail}" in ${c.controlType} "${c.name}"`,
|
|
52
|
+
// Table
|
|
53
|
+
"assert-row-count": (c) => `Assert ${c.controlType} "${c.name}" has ${c.expected} rows`,
|
|
54
|
+
"assert-row-contains": (c) => `Assert ${c.controlType} "${c.name}" has a row containing "${c.expected}"`,
|
|
55
|
+
// Dialog
|
|
56
|
+
close: (c) => `Close ${c.controlType} "${c.name}"`,
|
|
57
|
+
"assert-open": (c) => `Assert ${c.controlType} "${c.name}" is open`,
|
|
58
|
+
"assert-closed": (c) => `Assert ${c.controlType} "${c.name}" is closed`,
|
|
59
|
+
// Tabs
|
|
60
|
+
"open-tab": (c) => `Open tab "${c.detail}" in ${c.controlType} "${c.name}"`,
|
|
61
|
+
"assert-active-tab": (c) => `Assert ${c.controlType} "${c.name}" has active tab "${c.expected}"`,
|
|
62
|
+
// Image
|
|
63
|
+
"assert-alt": (c) => `Assert ${c.controlType} "${c.name}" has alt text "${c.expected}"`,
|
|
64
|
+
"assert-loaded": (c) => `Assert ${c.controlType} "${c.name}" is loaded`,
|
|
65
|
+
// DatePicker
|
|
66
|
+
"pick-date": (c) => `Pick date "${c.detail}" in ${c.controlType} "${c.name}"`,
|
|
67
|
+
// Collections
|
|
68
|
+
"assert-count": (c) => `Assert ${c.controlType} "${c.name}" appears ${c.expected} times`,
|
|
69
|
+
// Tooltip
|
|
70
|
+
"assert-tooltip": (c) => `Assert ${c.controlType} "${c.name}" says "${c.expected}"`
|
|
71
|
+
};
|
|
72
|
+
var IMPLICIT_ROLE = {
|
|
73
|
+
button: "button",
|
|
74
|
+
link: "link",
|
|
75
|
+
checkbox: "checkbox",
|
|
76
|
+
"radio group": "radiogroup",
|
|
77
|
+
switch: "switch",
|
|
78
|
+
field: "textbox",
|
|
79
|
+
"text area": "textbox",
|
|
80
|
+
// Single-choice <select>; a `multiple` select is a listbox and will simply
|
|
81
|
+
// not match the count()===1 guard — it fails safe instead of mis-healing.
|
|
82
|
+
select: "combobox",
|
|
83
|
+
combobox: "combobox",
|
|
84
|
+
slider: "slider",
|
|
85
|
+
dialog: "dialog",
|
|
86
|
+
menu: "menu",
|
|
87
|
+
tabs: "tablist",
|
|
88
|
+
image: "img",
|
|
89
|
+
table: "table"
|
|
90
|
+
};
|
|
91
|
+
function healingMode() {
|
|
92
|
+
const value = process.env.RAZO_HEALING;
|
|
93
|
+
return value === "fail" || value === "off" ? value : "pass";
|
|
94
|
+
}
|
|
95
|
+
function resolveLocator(root, spec) {
|
|
96
|
+
if (typeof spec === "string") return root.getByTestId(spec);
|
|
97
|
+
if ("testId" in spec) return root.getByTestId(spec.testId);
|
|
98
|
+
if ("role" in spec) return root.getByRole(spec.role, { name: spec.name, exact: spec.exact });
|
|
99
|
+
if ("label" in spec) return root.getByLabel(spec.label, { exact: spec.exact });
|
|
100
|
+
if ("placeholder" in spec) return root.getByPlaceholder(spec.placeholder);
|
|
101
|
+
if ("text" in spec) return root.getByText(spec.text, { exact: spec.exact });
|
|
102
|
+
if ("altText" in spec) return root.getByAltText(spec.altText);
|
|
103
|
+
if ("title" in spec) return root.getByTitle(spec.title);
|
|
104
|
+
if ("css" in spec) return root.locator(spec.css);
|
|
105
|
+
return spec.locator;
|
|
106
|
+
}
|
|
107
|
+
function describeSelector(spec) {
|
|
108
|
+
if (typeof spec === "string") return `[data-testid="${spec}"]`;
|
|
109
|
+
if ("testId" in spec) return `[data-testid="${spec.testId}"]`;
|
|
110
|
+
if ("role" in spec) return spec.name ? `role=${spec.role}[name="${spec.name}"]` : `role=${spec.role}`;
|
|
111
|
+
if ("label" in spec) return `label="${spec.label}"`;
|
|
112
|
+
if ("placeholder" in spec) return `placeholder="${spec.placeholder}"`;
|
|
113
|
+
if ("text" in spec) return `text="${spec.text}"`;
|
|
114
|
+
if ("altText" in spec) return `alt="${spec.altText}"`;
|
|
115
|
+
if ("title" in spec) return `title="${spec.title}"`;
|
|
116
|
+
if ("css" in spec) return `css=${spec.css}`;
|
|
117
|
+
return String(spec.locator);
|
|
118
|
+
}
|
|
119
|
+
var Control = class {
|
|
120
|
+
constructor(page, locate, name, options = {}) {
|
|
121
|
+
this.page = page;
|
|
122
|
+
this.name = name;
|
|
123
|
+
if (!name || !name.trim()) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Control located by ${describeSelector(locate)} needs a human-readable name: an anonymous control breaks the narration.`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
this.testId = typeof locate === "string" ? locate : "testId" in locate ? locate.testId : "";
|
|
129
|
+
this.parent = options.within;
|
|
130
|
+
this.fallbacks = options.fallbacks ?? [];
|
|
131
|
+
this._locator = resolveLocator(this.healRoot, locate);
|
|
132
|
+
this._selector = this.withinPrefix + describeSelector(locate);
|
|
133
|
+
}
|
|
134
|
+
page;
|
|
135
|
+
name;
|
|
136
|
+
/** @deprecated Only set when the control was located by testid; use `selector`. */
|
|
137
|
+
testId;
|
|
138
|
+
// Mutable internals so deterministic healing can re-point the control; the
|
|
139
|
+
// public surface stays read-only.
|
|
140
|
+
_locator;
|
|
141
|
+
_selector;
|
|
142
|
+
parent;
|
|
143
|
+
fallbacks;
|
|
144
|
+
/**
|
|
145
|
+
* Root that fallbacks resolve against — read lazily so a child control
|
|
146
|
+
* follows its `within` parent even after the PARENT healed.
|
|
147
|
+
*/
|
|
148
|
+
get healRoot() {
|
|
149
|
+
return this.parent?.locator ?? this.page;
|
|
150
|
+
}
|
|
151
|
+
get withinPrefix() {
|
|
152
|
+
return this.parent ? `${this.parent.selector} ` : "";
|
|
153
|
+
}
|
|
154
|
+
/** The Playwright locator currently backing this control. */
|
|
155
|
+
get locator() {
|
|
156
|
+
return this._locator;
|
|
157
|
+
}
|
|
158
|
+
/** Readable selector, chained through `within` parents; travels in every StepEvent. */
|
|
159
|
+
get selector() {
|
|
160
|
+
return this._selector;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Core of the framework: runs `fn` inside a test.step titled with the
|
|
164
|
+
* grammar sentence, and emits the matching structured StepEvent —
|
|
165
|
+
* also (especially) when it fails.
|
|
166
|
+
*/
|
|
167
|
+
async step(action, options, fn) {
|
|
168
|
+
const context = {
|
|
169
|
+
controlType: this.controlType,
|
|
170
|
+
name: this.name,
|
|
171
|
+
detail: options.detail,
|
|
172
|
+
expected: options.expected
|
|
173
|
+
};
|
|
174
|
+
const sentence = options.as ?? SENTENCES[action](context);
|
|
175
|
+
const base = {
|
|
176
|
+
action,
|
|
177
|
+
controlType: this.controlType,
|
|
178
|
+
name: this.name,
|
|
179
|
+
sentence,
|
|
180
|
+
detail: options.detail,
|
|
181
|
+
expected: options.expected
|
|
182
|
+
};
|
|
183
|
+
const fail = (error, extra = {}) => {
|
|
184
|
+
emitStepEvent({
|
|
185
|
+
...base,
|
|
186
|
+
...extra,
|
|
187
|
+
selector: this.selector,
|
|
188
|
+
status: "failed",
|
|
189
|
+
error: stripAnsi(error instanceof Error ? error.message : String(error)),
|
|
190
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
return test.step(sentence, async () => {
|
|
194
|
+
try {
|
|
195
|
+
const result = await fn();
|
|
196
|
+
emitStepEvent({
|
|
197
|
+
...base,
|
|
198
|
+
selector: this.selector,
|
|
199
|
+
status: "passed",
|
|
200
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
201
|
+
});
|
|
202
|
+
return result;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
const gone = healingMode() !== "off" && await this.primaryGone();
|
|
205
|
+
if (gone) {
|
|
206
|
+
const healedSpec = await this.findHealingSpec();
|
|
207
|
+
if (healedSpec) {
|
|
208
|
+
const healed = {
|
|
209
|
+
from: this.selector,
|
|
210
|
+
to: this.withinPrefix + describeSelector(healedSpec)
|
|
211
|
+
};
|
|
212
|
+
this._locator = resolveLocator(this.healRoot, healedSpec);
|
|
213
|
+
this._selector = healed.to;
|
|
214
|
+
if (healingMode() === "fail") {
|
|
215
|
+
const drift = new Error(
|
|
216
|
+
`locator drift: ${healed.from} no longer resolves; element found via ${healed.to} \u2014 update the locator`
|
|
217
|
+
);
|
|
218
|
+
fail(drift, { healed });
|
|
219
|
+
throw drift;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const result = await fn();
|
|
223
|
+
emitStepEvent({
|
|
224
|
+
...base,
|
|
225
|
+
selector: this.selector,
|
|
226
|
+
healed,
|
|
227
|
+
status: "passed",
|
|
228
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
229
|
+
});
|
|
230
|
+
return result;
|
|
231
|
+
} catch (retryError) {
|
|
232
|
+
const actual2 = options.readActual ? await options.readActual().catch(() => void 0) : void 0;
|
|
233
|
+
fail(retryError, { healed, actual: actual2 });
|
|
234
|
+
throw retryError;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const actual = options.readActual ? await options.readActual().catch(() => void 0) : void 0;
|
|
239
|
+
const domCandidates = gone ? await this.collectDomCandidates() : void 0;
|
|
240
|
+
fail(error, { actual, domCandidates });
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/** True when the primary locator currently matches nothing (safe: never throws). */
|
|
246
|
+
async primaryGone() {
|
|
247
|
+
try {
|
|
248
|
+
return await this._locator.count() === 0;
|
|
249
|
+
} catch {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/** First fallback (explicit, then implicit role+name) resolving to exactly one element. */
|
|
254
|
+
async findHealingSpec() {
|
|
255
|
+
const role = IMPLICIT_ROLE[this.controlType];
|
|
256
|
+
const candidates = [
|
|
257
|
+
...this.fallbacks,
|
|
258
|
+
// exact: getByRole's name matching is substring by default, which on a
|
|
259
|
+
// page with "Export" and "Confirm export…" would match both and block
|
|
260
|
+
// (or worse, mis-target) the heal. Exact accessible name only.
|
|
261
|
+
...role ? [{ role, name: this.name, exact: true }] : []
|
|
262
|
+
];
|
|
263
|
+
for (const spec of candidates) {
|
|
264
|
+
try {
|
|
265
|
+
if (await resolveLocator(this.healRoot, spec).count() === 1) return spec;
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Same-role elements on the page, as evidence for the analyzer to propose
|
|
273
|
+
* a replacement locator. Best-effort: capped, time-boxed, never throws.
|
|
274
|
+
*/
|
|
275
|
+
async collectDomCandidates() {
|
|
276
|
+
const role = IMPLICIT_ROLE[this.controlType];
|
|
277
|
+
if (!role) return void 0;
|
|
278
|
+
const collect = async () => {
|
|
279
|
+
const elements = (await this.healRoot.getByRole(role).all()).slice(0, 5);
|
|
280
|
+
const out = [];
|
|
281
|
+
for (const element of elements) {
|
|
282
|
+
const label = await element.getAttribute("aria-label") ?? await element.textContent() ?? "";
|
|
283
|
+
out.push(`role=${role} name="${label.trim().replace(/\s+/g, " ").slice(0, 120)}"`);
|
|
284
|
+
}
|
|
285
|
+
return out.length > 0 ? out : void 0;
|
|
286
|
+
};
|
|
287
|
+
const timeBox = new Promise((resolve) => {
|
|
288
|
+
setTimeout(() => resolve(void 0), 2e3).unref?.();
|
|
289
|
+
});
|
|
290
|
+
try {
|
|
291
|
+
return await Promise.race([collect().catch(() => void 0), timeBox]);
|
|
292
|
+
} catch {
|
|
293
|
+
return void 0;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async expectVisible(options = {}) {
|
|
297
|
+
await this.step(
|
|
298
|
+
"assert-visible",
|
|
299
|
+
{
|
|
300
|
+
...options,
|
|
301
|
+
expected: "visible",
|
|
302
|
+
readActual: async () => await this.locator.isVisible() ? "visible" : "hidden"
|
|
303
|
+
},
|
|
304
|
+
() => expect(this.locator).toBeVisible()
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
async expectEnabled(options = {}) {
|
|
308
|
+
await this.step(
|
|
309
|
+
"assert-enabled",
|
|
310
|
+
{
|
|
311
|
+
...options,
|
|
312
|
+
expected: "enabled",
|
|
313
|
+
readActual: async () => await this.locator.isEnabled() ? "enabled" : "disabled"
|
|
314
|
+
},
|
|
315
|
+
() => expect(this.locator).toBeEnabled()
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
async expectDisabled(options = {}) {
|
|
319
|
+
await this.step(
|
|
320
|
+
"assert-disabled",
|
|
321
|
+
{
|
|
322
|
+
...options,
|
|
323
|
+
expected: "disabled",
|
|
324
|
+
readActual: async () => await this.locator.isEnabled() ? "enabled" : "disabled"
|
|
325
|
+
},
|
|
326
|
+
() => expect(this.locator).toBeDisabled()
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
async expectText(expected, options = {}) {
|
|
330
|
+
await this.step(
|
|
331
|
+
"assert-text",
|
|
332
|
+
{
|
|
333
|
+
...options,
|
|
334
|
+
expected,
|
|
335
|
+
readActual: async () => (await this.locator.textContent() ?? "").trim()
|
|
336
|
+
},
|
|
337
|
+
() => expect(this.locator).toHaveText(expected)
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
/** The control as it appears in the narration: `button "Export"`. */
|
|
341
|
+
describe() {
|
|
342
|
+
return `${this.controlType} "${this.name}"`;
|
|
343
|
+
}
|
|
344
|
+
// --- universal interactions ---
|
|
345
|
+
async hover(options = {}) {
|
|
346
|
+
await this.step("hover", options, () => this.locator.hover());
|
|
347
|
+
}
|
|
348
|
+
async doubleClick(options = {}) {
|
|
349
|
+
await this.step("double-click", options, () => this.locator.dblclick());
|
|
350
|
+
}
|
|
351
|
+
async rightClick(options = {}) {
|
|
352
|
+
await this.step("right-click", options, () => this.locator.click({ button: "right" }));
|
|
353
|
+
}
|
|
354
|
+
async press(key, options = {}) {
|
|
355
|
+
await this.step("press", { ...options, detail: key }, () => this.locator.press(key));
|
|
356
|
+
}
|
|
357
|
+
async focus(options = {}) {
|
|
358
|
+
await this.step("focus", options, () => this.locator.focus());
|
|
359
|
+
}
|
|
360
|
+
async scrollTo(options = {}) {
|
|
361
|
+
await this.step("scroll-to", options, () => this.locator.scrollIntoViewIfNeeded());
|
|
362
|
+
}
|
|
363
|
+
async dragTo(target, options = {}) {
|
|
364
|
+
await this.step(
|
|
365
|
+
"drag",
|
|
366
|
+
{ ...options, detail: target.describe() },
|
|
367
|
+
() => this.locator.dragTo(target.locator)
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
// --- universal assertions ---
|
|
371
|
+
async expectHidden(options = {}) {
|
|
372
|
+
await this.step(
|
|
373
|
+
"assert-hidden",
|
|
374
|
+
{
|
|
375
|
+
...options,
|
|
376
|
+
expected: "hidden",
|
|
377
|
+
readActual: async () => await this.locator.isVisible() ? "visible" : "hidden"
|
|
378
|
+
},
|
|
379
|
+
() => expect(this.locator).toBeHidden()
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
async expectFocused(options = {}) {
|
|
383
|
+
await this.step(
|
|
384
|
+
"assert-focused",
|
|
385
|
+
{
|
|
386
|
+
...options,
|
|
387
|
+
expected: "focused",
|
|
388
|
+
readActual: () => this.locator.evaluate(
|
|
389
|
+
(el) => el === el.ownerDocument.activeElement ? "focused" : "not focused"
|
|
390
|
+
)
|
|
391
|
+
},
|
|
392
|
+
() => expect(this.locator).toBeFocused()
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
async expectContainsText(expected, options = {}) {
|
|
396
|
+
await this.step(
|
|
397
|
+
"assert-contains-text",
|
|
398
|
+
{
|
|
399
|
+
...options,
|
|
400
|
+
expected,
|
|
401
|
+
readActual: async () => (await this.locator.textContent() ?? "").trim()
|
|
402
|
+
},
|
|
403
|
+
() => expect(this.locator).toContainText(expected)
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
async expectAttribute(attribute, expected, options = {}) {
|
|
407
|
+
await this.step(
|
|
408
|
+
"assert-attribute",
|
|
409
|
+
{
|
|
410
|
+
...options,
|
|
411
|
+
detail: attribute,
|
|
412
|
+
expected,
|
|
413
|
+
readActual: async () => await this.locator.getAttribute(attribute) ?? "none"
|
|
414
|
+
},
|
|
415
|
+
() => expect(this.locator).toHaveAttribute(attribute, expected)
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
/** For repeated testids (list items): how many elements the control matches. */
|
|
419
|
+
async expectCount(expected, options = {}) {
|
|
420
|
+
await this.step(
|
|
421
|
+
"assert-count",
|
|
422
|
+
{
|
|
423
|
+
...options,
|
|
424
|
+
expected: String(expected),
|
|
425
|
+
readActual: async () => String(await this.locator.count())
|
|
426
|
+
},
|
|
427
|
+
() => expect(this.locator).toHaveCount(expected)
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// src/controls/Button.ts
|
|
433
|
+
var Button = class extends Control {
|
|
434
|
+
controlType = "button";
|
|
435
|
+
async click(options = {}) {
|
|
436
|
+
await this.step("click", options, () => this.locator.click());
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// src/controls/Input.ts
|
|
441
|
+
import { expect as expect2 } from "@playwright/test";
|
|
442
|
+
var Input = class extends Control {
|
|
443
|
+
controlType = "field";
|
|
444
|
+
/** For a field, "text" means its value, not its DOM content. */
|
|
445
|
+
async expectText(expected, options = {}) {
|
|
446
|
+
await this.step(
|
|
447
|
+
"assert-text",
|
|
448
|
+
{ ...options, expected, readActual: () => this.locator.inputValue() },
|
|
449
|
+
() => expect2(this.locator).toHaveValue(expected)
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
async fill(value, options = {}) {
|
|
453
|
+
await this.step("fill", { ...options, detail: value }, () => this.locator.fill(value));
|
|
454
|
+
}
|
|
455
|
+
async clear(options = {}) {
|
|
456
|
+
await this.step("clear", options, () => this.locator.clear());
|
|
457
|
+
}
|
|
458
|
+
async expectEmpty(options = {}) {
|
|
459
|
+
await this.step(
|
|
460
|
+
"assert-empty",
|
|
461
|
+
{ ...options, expected: "empty", readActual: () => this.locator.inputValue() },
|
|
462
|
+
() => expect2(this.locator).toHaveValue("")
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
// src/controls/RadioButton.ts
|
|
468
|
+
import { expect as expect3 } from "@playwright/test";
|
|
469
|
+
var RadioButton = class extends Control {
|
|
470
|
+
controlType = "radio group";
|
|
471
|
+
async select(option, options = {}) {
|
|
472
|
+
await this.step(
|
|
473
|
+
"select",
|
|
474
|
+
{ ...options, detail: option },
|
|
475
|
+
() => this.locator.getByRole("radio", { name: option }).check()
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
async expectSelected(option, options = {}) {
|
|
479
|
+
await this.step(
|
|
480
|
+
"assert-selected",
|
|
481
|
+
{
|
|
482
|
+
...options,
|
|
483
|
+
expected: option,
|
|
484
|
+
readActual: () => this.locator.locator('input[type="radio"]').evaluateAll(
|
|
485
|
+
(radios) => radios.find((r) => r.checked)?.value ?? "none"
|
|
486
|
+
)
|
|
487
|
+
},
|
|
488
|
+
() => expect3(this.locator.getByRole("radio", { name: option })).toBeChecked()
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// src/controls/Checkbox.ts
|
|
494
|
+
import { expect as expect4 } from "@playwright/test";
|
|
495
|
+
var Checkbox = class extends Control {
|
|
496
|
+
controlType = "checkbox";
|
|
497
|
+
readActualState = async () => await this.locator.isChecked() ? "checked" : "unchecked";
|
|
498
|
+
async expectChecked(options = {}) {
|
|
499
|
+
await this.step(
|
|
500
|
+
"assert-checked",
|
|
501
|
+
{ ...options, expected: "checked", readActual: this.readActualState },
|
|
502
|
+
() => expect4(this.locator).toBeChecked()
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
async expectUnchecked(options = {}) {
|
|
506
|
+
await this.step(
|
|
507
|
+
"assert-unchecked",
|
|
508
|
+
{ ...options, expected: "unchecked", readActual: this.readActualState },
|
|
509
|
+
() => expect4(this.locator).not.toBeChecked()
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
async check(options = {}) {
|
|
513
|
+
await this.step("check", options, () => this.locator.check());
|
|
514
|
+
}
|
|
515
|
+
async uncheck(options = {}) {
|
|
516
|
+
await this.step("uncheck", options, () => this.locator.uncheck());
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
// src/controls/Select.ts
|
|
521
|
+
var Select = class extends Control {
|
|
522
|
+
controlType = "select";
|
|
523
|
+
/** Chooses an option by its visible label. */
|
|
524
|
+
async choose(option, options = {}) {
|
|
525
|
+
await this.step("choose", { ...options, detail: option }, async () => {
|
|
526
|
+
await this.locator.selectOption({ label: option });
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
/** Chooses several options by label (for <select multiple>). */
|
|
530
|
+
async chooseMany(optionLabels, options = {}) {
|
|
531
|
+
await this.step("choose-many", { ...options, detail: optionLabels.join(", ") }, async () => {
|
|
532
|
+
await this.locator.selectOption(optionLabels.map((label) => ({ label })));
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
// src/controls/Link.ts
|
|
538
|
+
import { expect as expect5 } from "@playwright/test";
|
|
539
|
+
var Link = class extends Control {
|
|
540
|
+
controlType = "link";
|
|
541
|
+
async open(options = {}) {
|
|
542
|
+
await this.step("open", options, () => this.locator.click());
|
|
543
|
+
}
|
|
544
|
+
async expectHref(expected, options = {}) {
|
|
545
|
+
await this.step(
|
|
546
|
+
"assert-href",
|
|
547
|
+
{
|
|
548
|
+
...options,
|
|
549
|
+
expected,
|
|
550
|
+
readActual: async () => await this.locator.getAttribute("href") ?? "none"
|
|
551
|
+
},
|
|
552
|
+
() => expect5(this.locator).toHaveAttribute("href", expected)
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
// src/controls/Label.ts
|
|
558
|
+
var Label = class extends Control {
|
|
559
|
+
controlType = "label";
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
// src/controls/FileInput.ts
|
|
563
|
+
import * as path from "path";
|
|
564
|
+
import { expect as expect6 } from "@playwright/test";
|
|
565
|
+
var FileInput = class extends Control {
|
|
566
|
+
controlType = "file input";
|
|
567
|
+
/** Attaches one or more files by path. The narration shows the file names. */
|
|
568
|
+
async attach(files, options = {}) {
|
|
569
|
+
const list = Array.isArray(files) ? files : [files];
|
|
570
|
+
const names = list.map((f) => path.basename(f)).join(", ");
|
|
571
|
+
await this.step(
|
|
572
|
+
"attach",
|
|
573
|
+
{ ...options, detail: names },
|
|
574
|
+
() => this.locator.setInputFiles(list)
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
async clear(options = {}) {
|
|
578
|
+
await this.step("clear", options, () => this.locator.setInputFiles([]));
|
|
579
|
+
}
|
|
580
|
+
/** Asserts the name of the file currently selected. */
|
|
581
|
+
async expectFile(expectedName, options = {}) {
|
|
582
|
+
const readSelectedName = () => this.locator.evaluate(
|
|
583
|
+
(el) => el.files?.[0]?.name ?? "none"
|
|
584
|
+
);
|
|
585
|
+
await this.step(
|
|
586
|
+
"assert-value",
|
|
587
|
+
{ ...options, expected: expectedName, readActual: readSelectedName },
|
|
588
|
+
() => expect6.poll(readSelectedName).toBe(expectedName)
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
// src/controls/Slider.ts
|
|
594
|
+
import { expect as expect7 } from "@playwright/test";
|
|
595
|
+
var Slider = class extends Control {
|
|
596
|
+
controlType = "slider";
|
|
597
|
+
/**
|
|
598
|
+
* Sets the slider value. Range inputs cannot be filled, so the value is
|
|
599
|
+
* set in the DOM and the input/change events the app listens to are fired.
|
|
600
|
+
*/
|
|
601
|
+
async setValue(value, options = {}) {
|
|
602
|
+
const detail = String(value);
|
|
603
|
+
await this.step("set", { ...options, detail }, async () => {
|
|
604
|
+
await expect7(this.locator).toBeVisible();
|
|
605
|
+
await this.locator.evaluate((el, v) => {
|
|
606
|
+
const input = el;
|
|
607
|
+
input.value = v;
|
|
608
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
609
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
610
|
+
}, detail);
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
async expectValue(expected, options = {}) {
|
|
614
|
+
const value = String(expected);
|
|
615
|
+
await this.step(
|
|
616
|
+
"assert-value",
|
|
617
|
+
{ ...options, expected: value, readActual: () => this.locator.inputValue() },
|
|
618
|
+
() => expect7(this.locator).toHaveValue(value)
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
// src/controls/TextArea.ts
|
|
624
|
+
var TextArea = class extends Input {
|
|
625
|
+
controlType = "text area";
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
// src/controls/Switch.ts
|
|
629
|
+
import { expect as expect8 } from "@playwright/test";
|
|
630
|
+
var Switch = class extends Control {
|
|
631
|
+
controlType = "switch";
|
|
632
|
+
readState = async () => await this.locator.getAttribute("aria-checked") === "true" ? "on" : "off";
|
|
633
|
+
async turnOn(options = {}) {
|
|
634
|
+
await this.step("turn-on", options, async () => {
|
|
635
|
+
if (await this.readState() !== "on") await this.locator.click();
|
|
636
|
+
await expect8(this.locator).toHaveAttribute("aria-checked", "true");
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
async turnOff(options = {}) {
|
|
640
|
+
await this.step("turn-off", options, async () => {
|
|
641
|
+
if (await this.readState() !== "off") await this.locator.click();
|
|
642
|
+
await expect8(this.locator).toHaveAttribute("aria-checked", "false");
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
async expectOn(options = {}) {
|
|
646
|
+
await this.step(
|
|
647
|
+
"assert-on",
|
|
648
|
+
{ ...options, expected: "on", readActual: this.readState },
|
|
649
|
+
() => expect8(this.locator).toHaveAttribute("aria-checked", "true")
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
async expectOff(options = {}) {
|
|
653
|
+
await this.step(
|
|
654
|
+
"assert-off",
|
|
655
|
+
{ ...options, expected: "off", readActual: this.readState },
|
|
656
|
+
() => expect8(this.locator).toHaveAttribute("aria-checked", "false")
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
// src/controls/Combobox.ts
|
|
662
|
+
import { expect as expect9 } from "@playwright/test";
|
|
663
|
+
var Combobox = class extends Control {
|
|
664
|
+
controlType = "combobox";
|
|
665
|
+
async search(text, options = {}) {
|
|
666
|
+
await this.step("fill", { ...options, detail: text }, () => this.locator.fill(text));
|
|
667
|
+
}
|
|
668
|
+
async pick(option, options = {}) {
|
|
669
|
+
await this.step(
|
|
670
|
+
"pick",
|
|
671
|
+
{ ...options, detail: option },
|
|
672
|
+
() => this.page.getByRole("option", { name: option }).click()
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
async expectSelected(expected, options = {}) {
|
|
676
|
+
await this.step(
|
|
677
|
+
"assert-value",
|
|
678
|
+
{ ...options, expected, readActual: () => this.locator.inputValue() },
|
|
679
|
+
() => expect9(this.locator).toHaveValue(expected)
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
// src/controls/Table.ts
|
|
685
|
+
import { expect as expect10 } from "@playwright/test";
|
|
686
|
+
var Table = class extends Control {
|
|
687
|
+
controlType = "table";
|
|
688
|
+
rows() {
|
|
689
|
+
return this.locator.locator("tbody tr");
|
|
690
|
+
}
|
|
691
|
+
async expectRowCount(expected, options = {}) {
|
|
692
|
+
await this.step(
|
|
693
|
+
"assert-row-count",
|
|
694
|
+
{
|
|
695
|
+
...options,
|
|
696
|
+
expected: String(expected),
|
|
697
|
+
readActual: async () => String(await this.rows().count())
|
|
698
|
+
},
|
|
699
|
+
() => expect10(this.rows()).toHaveCount(expected)
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
async expectRowContains(expected, options = {}) {
|
|
703
|
+
await this.step(
|
|
704
|
+
"assert-row-contains",
|
|
705
|
+
{
|
|
706
|
+
...options,
|
|
707
|
+
expected,
|
|
708
|
+
readActual: async () => `${await this.rows().count()} rows, none matching`
|
|
709
|
+
},
|
|
710
|
+
() => expect10(this.rows().filter({ hasText: expected }).first()).toBeVisible()
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
// src/controls/Dialog.ts
|
|
716
|
+
import { expect as expect11 } from "@playwright/test";
|
|
717
|
+
var Dialog = class extends Control {
|
|
718
|
+
controlType = "dialog";
|
|
719
|
+
readState = async () => await this.locator.isVisible() ? "open" : "closed";
|
|
720
|
+
async close(options = {}) {
|
|
721
|
+
await this.step("close", options, async () => {
|
|
722
|
+
await this.page.keyboard.press("Escape");
|
|
723
|
+
await expect11(this.locator).toBeHidden();
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
async expectOpen(options = {}) {
|
|
727
|
+
await this.step(
|
|
728
|
+
"assert-open",
|
|
729
|
+
{ ...options, expected: "open", readActual: this.readState },
|
|
730
|
+
() => expect11(this.locator).toBeVisible()
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
async expectClosed(options = {}) {
|
|
734
|
+
await this.step(
|
|
735
|
+
"assert-closed",
|
|
736
|
+
{ ...options, expected: "closed", readActual: this.readState },
|
|
737
|
+
() => expect11(this.locator).toBeHidden()
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
// src/controls/Tabs.ts
|
|
743
|
+
import { expect as expect12 } from "@playwright/test";
|
|
744
|
+
var Tabs = class extends Control {
|
|
745
|
+
controlType = "tabs";
|
|
746
|
+
async open(tab, options = {}) {
|
|
747
|
+
await this.step(
|
|
748
|
+
"open-tab",
|
|
749
|
+
{ ...options, detail: tab },
|
|
750
|
+
() => this.locator.getByRole("tab", { name: tab }).click()
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
async expectActive(expected, options = {}) {
|
|
754
|
+
await this.step(
|
|
755
|
+
"assert-active-tab",
|
|
756
|
+
{
|
|
757
|
+
...options,
|
|
758
|
+
expected,
|
|
759
|
+
readActual: async () => (await this.locator.locator('[role="tab"][aria-selected="true"]').first().textContent() ?? "none").trim()
|
|
760
|
+
},
|
|
761
|
+
() => expect12(this.locator.getByRole("tab", { name: expected })).toHaveAttribute(
|
|
762
|
+
"aria-selected",
|
|
763
|
+
"true"
|
|
764
|
+
)
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
// src/controls/Image.ts
|
|
770
|
+
import { expect as expect13 } from "@playwright/test";
|
|
771
|
+
var Image = class extends Control {
|
|
772
|
+
controlType = "image";
|
|
773
|
+
async expectAlt(expected, options = {}) {
|
|
774
|
+
await this.step(
|
|
775
|
+
"assert-alt",
|
|
776
|
+
{
|
|
777
|
+
...options,
|
|
778
|
+
expected,
|
|
779
|
+
readActual: async () => await this.locator.getAttribute("alt") ?? "none"
|
|
780
|
+
},
|
|
781
|
+
() => expect13(this.locator).toHaveAttribute("alt", expected)
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
async expectLoaded(options = {}) {
|
|
785
|
+
const isLoaded = () => this.locator.evaluate(
|
|
786
|
+
(el) => el.complete && el.naturalWidth > 0
|
|
787
|
+
);
|
|
788
|
+
await this.step(
|
|
789
|
+
"assert-loaded",
|
|
790
|
+
{
|
|
791
|
+
...options,
|
|
792
|
+
expected: "loaded",
|
|
793
|
+
readActual: async () => await isLoaded() ? "loaded" : "not loaded"
|
|
794
|
+
},
|
|
795
|
+
() => expect13.poll(isLoaded).toBe(true)
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
// src/controls/DatePicker.ts
|
|
801
|
+
import { expect as expect14 } from "@playwright/test";
|
|
802
|
+
var DatePicker = class extends Control {
|
|
803
|
+
controlType = "date picker";
|
|
804
|
+
async pick(isoDate, options = {}) {
|
|
805
|
+
await this.step(
|
|
806
|
+
"pick-date",
|
|
807
|
+
{ ...options, detail: isoDate },
|
|
808
|
+
() => this.locator.fill(isoDate)
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
async expectDate(expected, options = {}) {
|
|
812
|
+
await this.step(
|
|
813
|
+
"assert-value",
|
|
814
|
+
{ ...options, expected, readActual: () => this.locator.inputValue() },
|
|
815
|
+
() => expect14(this.locator).toHaveValue(expected)
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
// src/controls/Menu.ts
|
|
821
|
+
var Menu = class extends Control {
|
|
822
|
+
controlType = "menu";
|
|
823
|
+
async choose(item, options = {}) {
|
|
824
|
+
await this.step("choose", { ...options, detail: item }, async () => {
|
|
825
|
+
await this.locator.click();
|
|
826
|
+
await this.page.getByRole("menuitem", { name: item }).click();
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
// src/controls/Tooltip.ts
|
|
832
|
+
import { expect as expect15 } from "@playwright/test";
|
|
833
|
+
var Tooltip = class extends Control {
|
|
834
|
+
controlType = "tooltip";
|
|
835
|
+
async tooltipText() {
|
|
836
|
+
const id = await this.locator.getAttribute("aria-describedby");
|
|
837
|
+
if (!id) return "no tooltip";
|
|
838
|
+
const text = await this.page.locator(`#${id}`).textContent();
|
|
839
|
+
return (text ?? "").trim() || "no tooltip";
|
|
840
|
+
}
|
|
841
|
+
/** Hovers the trigger and asserts the tooltip content. */
|
|
842
|
+
async expectContent(expected, options = {}) {
|
|
843
|
+
await this.step(
|
|
844
|
+
"assert-tooltip",
|
|
845
|
+
{ ...options, expected, readActual: () => this.tooltipText() },
|
|
846
|
+
async () => {
|
|
847
|
+
await this.locator.hover();
|
|
848
|
+
const id = await this.locator.getAttribute("aria-describedby");
|
|
849
|
+
if (!id) throw new Error("trigger has no aria-describedby tooltip");
|
|
850
|
+
await expect15(this.page.locator(`#${id}`)).toBeVisible();
|
|
851
|
+
await expect15(this.page.locator(`#${id}`)).toHaveText(expected);
|
|
852
|
+
}
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
// src/controls/Editor.ts
|
|
858
|
+
var Editor = class extends Control {
|
|
859
|
+
controlType = "editor";
|
|
860
|
+
async write(text, options = {}) {
|
|
861
|
+
await this.step("fill", { ...options, detail: text }, () => this.locator.fill(text));
|
|
862
|
+
}
|
|
863
|
+
async clear(options = {}) {
|
|
864
|
+
await this.step("clear", options, () => this.locator.fill(""));
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
export {
|
|
868
|
+
AI_STEP_ATTACHMENT,
|
|
869
|
+
Button,
|
|
870
|
+
Checkbox,
|
|
871
|
+
Combobox,
|
|
872
|
+
Control,
|
|
873
|
+
DatePicker,
|
|
874
|
+
Dialog,
|
|
875
|
+
Editor,
|
|
876
|
+
FileInput,
|
|
877
|
+
Image,
|
|
878
|
+
Input,
|
|
879
|
+
Label,
|
|
880
|
+
Link,
|
|
881
|
+
Menu,
|
|
882
|
+
RadioButton,
|
|
883
|
+
Select,
|
|
884
|
+
Slider,
|
|
885
|
+
Switch,
|
|
886
|
+
Table,
|
|
887
|
+
Tabs,
|
|
888
|
+
TextArea,
|
|
889
|
+
Tooltip,
|
|
890
|
+
emitStepEvent
|
|
891
|
+
};
|
|
892
|
+
//# sourceMappingURL=index.mjs.map
|