xmlui 0.10.26 → 0.11.1
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/dist/lib/{index-DHXWMb-6.mjs → index-CEq6OdjV.js} +263 -643
- package/dist/lib/index.css +1 -1
- package/dist/lib/{initMock-TxnkId6_.mjs → initMock-DhUnLKrR.js} +1 -1
- package/dist/lib/{language-server-web-worker.mjs → language-server-web-worker.js} +1 -1
- package/dist/lib/{language-server.mjs → language-server.js} +1 -1
- package/dist/lib/{metadata-utils-DXUdlyja.mjs → metadata-utils-D27cn-XB.js} +1 -1
- package/dist/lib/{server-common-CtpN0Z4h.mjs → server-common-2DaoOOL5.js} +625 -616
- package/dist/lib/testing.d.ts +2011 -0
- package/dist/lib/testing.js +2386 -0
- package/dist/lib/vite-xmlui-plugin/index.js +13968 -0
- package/dist/lib/vite-xmlui-plugin/package.json +3 -0
- package/dist/lib/xmlui-parser-BZZ430Wm.js +523 -0
- package/dist/lib/xmlui-parser.d.ts +2 -1
- package/dist/lib/{xmlui-parser.mjs → xmlui-parser.js} +2 -2
- package/dist/lib/{xmlui-serializer-uCYa8_tZ.mjs → xmlui-serializer-D9D2mQ8m.js} +1 -1
- package/dist/lib/xmlui.d.ts +1 -0
- package/dist/lib/{xmlui.mjs → xmlui.js} +24 -23
- package/dist/metadata/{collectedComponentMetadata-BgHIc2Iu.mjs → collectedComponentMetadata-BAI5eK2v.js} +698 -567
- package/dist/metadata/{initMock-B3UDa-rw.mjs → initMock-CekNG5Ax.js} +1 -1
- package/dist/metadata/style.css +1 -1
- package/dist/metadata/{xmlui-metadata.mjs → xmlui-metadata.js} +1 -1
- package/dist/metadata/xmlui-metadata.umd.cjs +207 -0
- package/dist/scripts/bin/bootstrap.cjs +4 -0
- package/dist/scripts/bin/index.js +85 -13
- package/dist/scripts/package.json +30 -22
- package/dist/scripts/src/components/App/App.spec.js +27 -15
- package/dist/scripts/src/components/Avatar/Avatar.spec.js +0 -29
- package/dist/scripts/src/components/Button/Button.spec.js +0 -29
- package/dist/scripts/src/components/Charts/BarChart/BarChartNative.js +2 -0
- package/dist/scripts/src/components/Charts/LineChart/LineChartNative.js +2 -2
- package/dist/scripts/src/components/Charts/Tooltip/TooltipContent.spec.js +8 -6
- package/dist/scripts/src/components/Form/Form.js +19 -0
- package/dist/scripts/src/components/Form/Form.spec.js +444 -0
- package/dist/scripts/src/components/Form/FormNative.js +46 -15
- package/dist/scripts/src/components/Form/formActions.js +3 -2
- package/dist/scripts/src/components/FormItem/FormItem.js +10 -2
- package/dist/scripts/src/components/FormItem/FormItem.spec.js +159 -0
- package/dist/scripts/src/components/FormItem/FormItemNative.js +6 -5
- package/dist/scripts/src/components/Heading/Heading.spec.js +34 -47
- package/dist/scripts/src/components/Queue/Queue.js +1 -16
- package/dist/scripts/src/components/Queue/QueueNative.js +60 -2
- package/dist/scripts/src/components/TableOfContents/TableOfContents.js +7 -5
- package/dist/scripts/src/components-core/appContext/misc-utils.js +2 -1
- package/dist/scripts/src/components-core/devtools/InspectorDialog.js +2 -2
- package/dist/scripts/src/components-core/script-runner/eval-tree-async.js +2 -0
- package/dist/scripts/src/components-core/utils/base64-utils.js +2 -0
- package/dist/scripts/src/components-core/utils/misc.js +44 -0
- package/dist/scripts/src/language-server/server-common.js +2 -2
- package/dist/scripts/src/language-server/{xmlui-metadata-generated.mjs → xmlui-metadata-generated.js} +625 -615
- package/dist/scripts/src/testing/drivers/index.js +9 -0
- package/dist/scripts/src/testing/index.js +69 -0
- package/dist/standalone/xmlui-standalone.es.d.ts +32 -16
- package/dist/standalone/xmlui-standalone.umd.js +36 -36
- package/package.json +45 -37
- package/dist/metadata/xmlui-metadata.umd.js +0 -207
- package/dist/scripts/bin/bootstrap.js +0 -11
- /package/dist/lib/{apiInterceptorWorker-QiltRtq1.mjs → apiInterceptorWorker-QiltRtq1.js} +0 -0
- /package/dist/lib/{syntax-monaco.mjs → syntax-monaco.js} +0 -0
- /package/dist/lib/{syntax-textmate.mjs → syntax-textmate.js} +0 -0
- /package/dist/lib/{transform-Tooy42EB.mjs → transform-Tooy42EB.js} +0 -0
- /package/dist/metadata/{apiInterceptorWorker-Dql7QGw2.mjs → apiInterceptorWorker-Dql7QGw2.js} +0 -0
|
@@ -0,0 +1,2386 @@
|
|
|
1
|
+
var _a;
|
|
2
|
+
import { expect as expect$1, test as test$1 } from "@playwright/test";
|
|
3
|
+
import { x as xmlUiMarkupToComponent, P as PART_START_ADORNMENT, a as PART_END_ADORNMENT, b as PART_INPUT } from "./xmlui-parser-BZZ430Wm.js";
|
|
4
|
+
import chroma from "chroma-js";
|
|
5
|
+
function mapObject(obj, valFn = (val) => val, keyFn = (val) => val) {
|
|
6
|
+
const newObject = {};
|
|
7
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
8
|
+
newObject[keyFn(key)] = valFn(value);
|
|
9
|
+
});
|
|
10
|
+
return newObject;
|
|
11
|
+
}
|
|
12
|
+
function getComponentTagName(locator) {
|
|
13
|
+
return locator.evaluate((el) => el.tagName.toLowerCase());
|
|
14
|
+
}
|
|
15
|
+
function parseComponentIfNecessary(rawComponent) {
|
|
16
|
+
if (typeof rawComponent === "string") {
|
|
17
|
+
return xmlUiMarkupToComponent(rawComponent);
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
component: rawComponent,
|
|
21
|
+
errors: [],
|
|
22
|
+
erroneousCompoundComponentName: void 0
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function scaleByPercent(scalarOf100Percent, percentage) {
|
|
26
|
+
const parsed = parseAsNumericCss(percentage);
|
|
27
|
+
if (parsed.unit !== "%") {
|
|
28
|
+
throw new Error(`Expected percentage unit (%), got: ${parsed.unit}`);
|
|
29
|
+
}
|
|
30
|
+
return scalarOf100Percent / 100 * parsed.value;
|
|
31
|
+
}
|
|
32
|
+
function getBoundingRect$1(locator) {
|
|
33
|
+
return locator.evaluate((element) => element.getBoundingClientRect());
|
|
34
|
+
}
|
|
35
|
+
function getElementStyle$1(specifier, style) {
|
|
36
|
+
return specifier.evaluate(
|
|
37
|
+
(element, style2) => window.getComputedStyle(element).getPropertyValue(style2),
|
|
38
|
+
style
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
function getElementStyles$1(locator, styles = []) {
|
|
42
|
+
return locator.evaluate(
|
|
43
|
+
(element, styles2) => Object.fromEntries(
|
|
44
|
+
styles2.map((styleName) => [
|
|
45
|
+
styleName,
|
|
46
|
+
window.getComputedStyle(element).getPropertyValue(styleName)
|
|
47
|
+
])
|
|
48
|
+
),
|
|
49
|
+
styles
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
function isIndeterminate(specifier) {
|
|
53
|
+
if (specifier instanceof ComponentDriver) specifier = specifier.component;
|
|
54
|
+
return specifier.evaluate((el) => el.indeterminate);
|
|
55
|
+
}
|
|
56
|
+
async function overflows(locator, direction = "both") {
|
|
57
|
+
const [width, height, scrollWidth, scrollHeight] = await locator.evaluate((element) => [
|
|
58
|
+
element.clientWidth,
|
|
59
|
+
element.clientHeight,
|
|
60
|
+
element.scrollWidth,
|
|
61
|
+
element.scrollHeight
|
|
62
|
+
]);
|
|
63
|
+
if (direction === "x") return scrollWidth > width;
|
|
64
|
+
if (direction === "y") return scrollHeight > height;
|
|
65
|
+
return scrollWidth > width && scrollHeight > height;
|
|
66
|
+
}
|
|
67
|
+
function getStyles(specifier, style) {
|
|
68
|
+
if (specifier instanceof ComponentDriver) specifier = specifier.component;
|
|
69
|
+
style = Array.isArray(style) ? style : [style];
|
|
70
|
+
return specifier.evaluate(
|
|
71
|
+
(element, styles) => Object.fromEntries(
|
|
72
|
+
styles.map((styleName) => [
|
|
73
|
+
styleName.trim().split("-").map((n, idx) => idx === 0 ? n : n[0].toUpperCase() + n.slice(1)).join(""),
|
|
74
|
+
window.getComputedStyle(element).getPropertyValue(styleName)
|
|
75
|
+
])
|
|
76
|
+
),
|
|
77
|
+
style
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
function getPseudoStyles(specifier, pseudoElement, style) {
|
|
81
|
+
if (specifier instanceof ComponentDriver) specifier = specifier.component;
|
|
82
|
+
style = Array.isArray(style) ? style : [style];
|
|
83
|
+
return specifier.evaluate(
|
|
84
|
+
(element, obj) => Object.fromEntries(
|
|
85
|
+
obj.style.map((styleName) => [
|
|
86
|
+
styleName.trim().split("-").map((n, idx) => idx === 0 ? n : n[0].toUpperCase() + n.slice(1)).join(""),
|
|
87
|
+
window.getComputedStyle(element, obj.pseudoElement).getPropertyValue(styleName)
|
|
88
|
+
])
|
|
89
|
+
),
|
|
90
|
+
{ style, pseudoElement }
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
async function getHtmlAttributes(specifier, attributes) {
|
|
94
|
+
if (specifier instanceof ComponentDriver) specifier = specifier.component;
|
|
95
|
+
attributes = Array.isArray(attributes) ? attributes : [attributes];
|
|
96
|
+
const mapped = await Promise.all(
|
|
97
|
+
attributes.map(async (attr) => {
|
|
98
|
+
return [attr, await specifier.getAttribute(attr)];
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
return Object.fromEntries(mapped);
|
|
102
|
+
}
|
|
103
|
+
async function getPaddings(specifier) {
|
|
104
|
+
const paddings = mapObject(
|
|
105
|
+
await getStyles(specifier, ["padding-left", "padding-right", "padding-top", "padding-bottom"]),
|
|
106
|
+
parseAsNumericCss
|
|
107
|
+
);
|
|
108
|
+
return {
|
|
109
|
+
left: paddings.paddingLeft,
|
|
110
|
+
right: paddings.paddingRight,
|
|
111
|
+
top: paddings.paddingTop,
|
|
112
|
+
bottom: paddings.paddingBottom
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
async function getBorders(specifier) {
|
|
116
|
+
const borders = mapObject(
|
|
117
|
+
await getStyles(specifier, ["border-left", "border-right", "border-top", "border-bottom"]),
|
|
118
|
+
parseAsCssBorder
|
|
119
|
+
);
|
|
120
|
+
return {
|
|
121
|
+
left: borders.borderLeft,
|
|
122
|
+
right: borders.borderRight,
|
|
123
|
+
top: borders.borderTop,
|
|
124
|
+
bottom: borders.borderBottom
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function getMargins(specifier) {
|
|
128
|
+
return getStyles(specifier, ["margin-left", "margin-right", "margin-top", "margin-bottom"]);
|
|
129
|
+
}
|
|
130
|
+
async function getBounds(specifier) {
|
|
131
|
+
if (specifier instanceof ComponentDriver) specifier = specifier.component;
|
|
132
|
+
const boundingRect = await specifier.evaluate((element) => element.getBoundingClientRect());
|
|
133
|
+
const m = mapObject(await getMargins(specifier), parseFloat);
|
|
134
|
+
const width = boundingRect.width + m.marginLeft + m.marginRight;
|
|
135
|
+
const height = boundingRect.height + m.marginTop + m.marginBottom;
|
|
136
|
+
const left = boundingRect.left - m.marginLeft;
|
|
137
|
+
const right = boundingRect.right + m.marginRight;
|
|
138
|
+
const top = boundingRect.top - m.marginTop;
|
|
139
|
+
const bottom = boundingRect.bottom + m.marginBottom;
|
|
140
|
+
return {
|
|
141
|
+
width,
|
|
142
|
+
height,
|
|
143
|
+
left,
|
|
144
|
+
right,
|
|
145
|
+
top,
|
|
146
|
+
bottom
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
class TestSkipReason {
|
|
150
|
+
addAnnotation(type, description) {
|
|
151
|
+
return {
|
|
152
|
+
annotation: {
|
|
153
|
+
type,
|
|
154
|
+
description: description ?? ""
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
NOT_IMPLEMENTED_XMLUI(description) {
|
|
159
|
+
return this.addAnnotation("not implemented in xmlui", description);
|
|
160
|
+
}
|
|
161
|
+
TO_BE_IMPLEMENTED(description) {
|
|
162
|
+
return this.addAnnotation("to be implemented", description);
|
|
163
|
+
}
|
|
164
|
+
XMLUI_BUG(description) {
|
|
165
|
+
return this.addAnnotation("xmlui bug", description);
|
|
166
|
+
}
|
|
167
|
+
TEST_INFRA_BUG(description) {
|
|
168
|
+
return this.addAnnotation("test infra bug", description);
|
|
169
|
+
}
|
|
170
|
+
TEST_NOT_WORKING(description) {
|
|
171
|
+
return this.addAnnotation("test not working", description);
|
|
172
|
+
}
|
|
173
|
+
TEST_INFRA_NOT_IMPLEMENTED(description) {
|
|
174
|
+
return this.addAnnotation("test infra not implemented", description);
|
|
175
|
+
}
|
|
176
|
+
REFACTOR(description) {
|
|
177
|
+
return this.addAnnotation("refactor", description);
|
|
178
|
+
}
|
|
179
|
+
// Need to specify a reason here!
|
|
180
|
+
UNSURE(description) {
|
|
181
|
+
return this.addAnnotation("unsure", description);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const SKIP_REASON = new TestSkipReason();
|
|
185
|
+
function pixelStrToNum$1(pixelStr) {
|
|
186
|
+
return Number(pixelStr.replace("px", ""));
|
|
187
|
+
}
|
|
188
|
+
function parseAsCssBorder(str) {
|
|
189
|
+
const parts = str.split(/\s+(?=[a-z]+|\()/i);
|
|
190
|
+
if (parts.length > 3) {
|
|
191
|
+
throw new Error(`Provided value ${str} cannot be parsed as a CSS border`);
|
|
192
|
+
}
|
|
193
|
+
const style = parts.filter(isCSSBorderStyle);
|
|
194
|
+
if (style.length > 1) {
|
|
195
|
+
throw new Error(`Too many border styles provided in ${str}`);
|
|
196
|
+
}
|
|
197
|
+
const width = parts.filter(isNumericCSS);
|
|
198
|
+
if (width.length > 1) {
|
|
199
|
+
throw new Error(`Too many border widths provided in ${str}`);
|
|
200
|
+
}
|
|
201
|
+
const color = parts.filter((p) => chroma.valid(p));
|
|
202
|
+
if (color.length > 1) {
|
|
203
|
+
throw new Error(`Too many border colors provided in ${str}`);
|
|
204
|
+
}
|
|
205
|
+
const result = {
|
|
206
|
+
width: !!width[0] ? parseAsNumericCss(width[0]) : void 0,
|
|
207
|
+
style: style[0],
|
|
208
|
+
color: chroma(color[0])
|
|
209
|
+
};
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
const numericCSSRegex = /^([+-]?(?:\d+|\d*\.\d+))([a-z]*|%)$/;
|
|
213
|
+
function isNumericCSS(str) {
|
|
214
|
+
const parts = str.match(numericCSSRegex);
|
|
215
|
+
if (!parts) return false;
|
|
216
|
+
if (parts.length < 3) return false;
|
|
217
|
+
if (isNaN(parseFloat(parts[1]))) return false;
|
|
218
|
+
if (!isCSSUnit(parts[2])) return false;
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
function parseAsNumericCss(str) {
|
|
222
|
+
const parts = str.match(numericCSSRegex);
|
|
223
|
+
if (!parts) {
|
|
224
|
+
throw new Error(`Provided value ${str} cannot be parsed as a numeric CSS value`);
|
|
225
|
+
}
|
|
226
|
+
if (parts.length < 3) {
|
|
227
|
+
throw new Error(`${parts[0]} is not a correct numeric CSS value`);
|
|
228
|
+
}
|
|
229
|
+
const value = parseFloat(parts[1]);
|
|
230
|
+
if (isNaN(value)) {
|
|
231
|
+
throw new Error(`${value} is not a valid number in ${str}`);
|
|
232
|
+
}
|
|
233
|
+
const unit = parts[2];
|
|
234
|
+
if (!isCSSUnit(unit)) {
|
|
235
|
+
throw new Error(`${unit} is not have a valid CSS unit in ${str}`);
|
|
236
|
+
}
|
|
237
|
+
const result = { value, unit };
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
function numericCSSToString(cssValue) {
|
|
241
|
+
return `${cssValue.value}${cssValue.unit}`;
|
|
242
|
+
}
|
|
243
|
+
function parseAsCSSColor(str) {
|
|
244
|
+
return chroma(str);
|
|
245
|
+
}
|
|
246
|
+
const CSSUnitValues = [
|
|
247
|
+
"px",
|
|
248
|
+
"em",
|
|
249
|
+
"rem",
|
|
250
|
+
"vh",
|
|
251
|
+
"vw",
|
|
252
|
+
"%",
|
|
253
|
+
"cm",
|
|
254
|
+
"mm",
|
|
255
|
+
"in",
|
|
256
|
+
"pt",
|
|
257
|
+
"pc",
|
|
258
|
+
"ex",
|
|
259
|
+
"ch",
|
|
260
|
+
"vmin",
|
|
261
|
+
"vmax"
|
|
262
|
+
];
|
|
263
|
+
function isCSSUnit(str) {
|
|
264
|
+
return CSSUnitValues.includes(str);
|
|
265
|
+
}
|
|
266
|
+
const CSSBorderStyleValues = [
|
|
267
|
+
"solid",
|
|
268
|
+
"dotted",
|
|
269
|
+
"dashed",
|
|
270
|
+
"double",
|
|
271
|
+
"none",
|
|
272
|
+
"hidden",
|
|
273
|
+
"groove",
|
|
274
|
+
"ridge",
|
|
275
|
+
"inset",
|
|
276
|
+
"outset"
|
|
277
|
+
];
|
|
278
|
+
function isCSSBorderStyle(str) {
|
|
279
|
+
return CSSBorderStyleValues.includes(str);
|
|
280
|
+
}
|
|
281
|
+
function parseAsCSSBorderStyle(str) {
|
|
282
|
+
if (!isCSSBorderStyle(str)) {
|
|
283
|
+
throw new Error(`Provided value ${str} cannot be parsed as a CSS border style`);
|
|
284
|
+
}
|
|
285
|
+
return str;
|
|
286
|
+
}
|
|
287
|
+
const BorderSideValues = ["top", "bottom", "left", "right"];
|
|
288
|
+
function isBorderSide(str) {
|
|
289
|
+
return BorderSideValues.includes(str);
|
|
290
|
+
}
|
|
291
|
+
class ComponentDriver {
|
|
292
|
+
constructor({ locator, page }) {
|
|
293
|
+
this.click = async (options) => {
|
|
294
|
+
await this.locator.click(options);
|
|
295
|
+
};
|
|
296
|
+
this.dblclick = async (options) => {
|
|
297
|
+
await this.locator.dblclick(options);
|
|
298
|
+
};
|
|
299
|
+
this.focus = async (options) => {
|
|
300
|
+
await this.locator.focus(options);
|
|
301
|
+
};
|
|
302
|
+
this.blur = async (options) => {
|
|
303
|
+
await this.locator.blur(options);
|
|
304
|
+
};
|
|
305
|
+
this.hover = async (options) => {
|
|
306
|
+
await this.locator.hover(options);
|
|
307
|
+
};
|
|
308
|
+
this.locator = locator;
|
|
309
|
+
this.page = page;
|
|
310
|
+
}
|
|
311
|
+
get component() {
|
|
312
|
+
return this.locator;
|
|
313
|
+
}
|
|
314
|
+
getByTestId(testId) {
|
|
315
|
+
return this.component.getByTestId(testId).first();
|
|
316
|
+
}
|
|
317
|
+
getByPartName(part) {
|
|
318
|
+
return this.component.locator(`[data-part-id="${part}"]`).first();
|
|
319
|
+
}
|
|
320
|
+
getIcons() {
|
|
321
|
+
return this.component.locator('[data-icon-name="*"]');
|
|
322
|
+
}
|
|
323
|
+
getIconsByName(name) {
|
|
324
|
+
return this.component.locator(`[data-icon-name="${name}"]`);
|
|
325
|
+
}
|
|
326
|
+
getIconByName(name) {
|
|
327
|
+
return this.component.locator(`[data-icon-name="${name}"]`).first();
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Gets the html tag name of the final rendered component
|
|
331
|
+
*/
|
|
332
|
+
getComponentTagName() {
|
|
333
|
+
return this.component.evaluate((el) => el.tagName.toLowerCase());
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
class InputComponentDriver extends ComponentDriver {
|
|
337
|
+
get field() {
|
|
338
|
+
return this.getByPartName("input");
|
|
339
|
+
}
|
|
340
|
+
get label() {
|
|
341
|
+
return this.getByPartName("label");
|
|
342
|
+
}
|
|
343
|
+
get placeholder() {
|
|
344
|
+
return this.field.getAttribute("placeholder");
|
|
345
|
+
}
|
|
346
|
+
get requiredIndicator() {
|
|
347
|
+
return this.component.getByText("*");
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
class TestStateDriver {
|
|
351
|
+
constructor(testStateLocator) {
|
|
352
|
+
this.testStateLocator = testStateLocator;
|
|
353
|
+
}
|
|
354
|
+
/** returns an async function that can query the test state */
|
|
355
|
+
get testState() {
|
|
356
|
+
return async () => {
|
|
357
|
+
const text = await this.testStateLocator.textContent();
|
|
358
|
+
const testState = text === "undefined" ? void 0 : JSON.parse(text);
|
|
359
|
+
return testState;
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
class ButtonDriver extends ComponentDriver {
|
|
364
|
+
// Ensure we either get rtl or ltr strings
|
|
365
|
+
/* async getWritingDirection() {
|
|
366
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir
|
|
367
|
+
const attribute = await this.locator.getAttribute("dir");
|
|
368
|
+
if (attribute && attribute !== "auto") return attribute as "rtl" | "ltr";
|
|
369
|
+
const style = await this.locator.evaluate(
|
|
370
|
+
(element) => window.getComputedStyle(element).direction,
|
|
371
|
+
);
|
|
372
|
+
// Default is ltr: https://developer.mozilla.org/en-US/docs/Web/CSS/direction#values
|
|
373
|
+
return style === "rtl" ? "rtl" : "ltr";
|
|
374
|
+
} */
|
|
375
|
+
// Unused as of yet
|
|
376
|
+
/* async getTextNodes() {
|
|
377
|
+
return await this.locator.evaluate((element) =>
|
|
378
|
+
[...element.childNodes]
|
|
379
|
+
.filter((e) => e.nodeType === Node.TEXT_NODE && e.textContent.trim())
|
|
380
|
+
.map((e) => e.textContent.trim()),
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
*/
|
|
384
|
+
getFirstNonTextNode() {
|
|
385
|
+
return this.locator.locator("> *").first();
|
|
386
|
+
}
|
|
387
|
+
// NOTE: Accounts for icons being passed as children as well
|
|
388
|
+
getIcons() {
|
|
389
|
+
return this.locator.locator("> svg").or(this.locator.locator("> img"));
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
class ContentSeparatorDriver extends ComponentDriver {
|
|
393
|
+
get separator() {
|
|
394
|
+
return this.component;
|
|
395
|
+
}
|
|
396
|
+
async getOrientation() {
|
|
397
|
+
const classList = await this.separator.evaluate((el) => el.className);
|
|
398
|
+
if (classList.includes("horizontal")) return "horizontal";
|
|
399
|
+
if (classList.includes("vertical")) return "vertical";
|
|
400
|
+
return "unknown";
|
|
401
|
+
}
|
|
402
|
+
async getComputedHeight() {
|
|
403
|
+
return await this.separator.evaluate((el) => {
|
|
404
|
+
return window.getComputedStyle(el).height;
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
async getComputedWidth() {
|
|
408
|
+
return await this.separator.evaluate((el) => {
|
|
409
|
+
return window.getComputedStyle(el).width;
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
async getBackgroundColor() {
|
|
413
|
+
return await this.separator.evaluate((el) => {
|
|
414
|
+
return window.getComputedStyle(el).backgroundColor;
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
class AvatarDriver extends ComponentDriver {
|
|
419
|
+
}
|
|
420
|
+
class SplitterDriver extends ComponentDriver {
|
|
421
|
+
/**
|
|
422
|
+
* Gets the resizer element (non-floating)
|
|
423
|
+
*/
|
|
424
|
+
async getResizer() {
|
|
425
|
+
const allResizerCandidates = this.locator.locator('[class*="resizer"]');
|
|
426
|
+
const resizerCount = await allResizerCandidates.count();
|
|
427
|
+
for (let i = 0; i < resizerCount; i++) {
|
|
428
|
+
const candidate = allResizerCandidates.nth(i);
|
|
429
|
+
const className = await candidate.getAttribute("class");
|
|
430
|
+
if (className && className.includes("resizer") && !className.includes("floating")) {
|
|
431
|
+
return candidate;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return allResizerCandidates.first();
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Gets the floating resizer element
|
|
438
|
+
*/
|
|
439
|
+
async getFloatingResizer() {
|
|
440
|
+
const allResizerCandidates = this.locator.locator('[class*="resizer"]');
|
|
441
|
+
const resizerCount = await allResizerCandidates.count();
|
|
442
|
+
for (let i = 0; i < resizerCount; i++) {
|
|
443
|
+
const candidate = allResizerCandidates.nth(i);
|
|
444
|
+
const className = await candidate.getAttribute("class");
|
|
445
|
+
if (className && className.includes("floating")) {
|
|
446
|
+
return candidate;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return this.locator.locator('[class*="floating"][class*="resizer"]').first();
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Hovers near the resizer area to trigger floating resizer visibility
|
|
453
|
+
*/
|
|
454
|
+
async hoverNearResizer() {
|
|
455
|
+
const bounds = await this.locator.boundingBox();
|
|
456
|
+
if (bounds) {
|
|
457
|
+
await this.page.mouse.move(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
|
|
458
|
+
await this.page.waitForTimeout(200);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Drags the resizer by the specified offset
|
|
463
|
+
* @param deltaX - Horizontal offset in pixels
|
|
464
|
+
* @param deltaY - Vertical offset in pixels
|
|
465
|
+
*/
|
|
466
|
+
async dragResizer(deltaX, deltaY) {
|
|
467
|
+
const resizer = await this.getResizer();
|
|
468
|
+
const floatingResizer = await this.getFloatingResizer();
|
|
469
|
+
let targetResizer = resizer;
|
|
470
|
+
try {
|
|
471
|
+
const isResizerVisible = await resizer.isVisible();
|
|
472
|
+
const isFloatingResizerVisible = await floatingResizer.isVisible();
|
|
473
|
+
if (!isResizerVisible && isFloatingResizerVisible) {
|
|
474
|
+
targetResizer = floatingResizer;
|
|
475
|
+
} else if (!isResizerVisible) {
|
|
476
|
+
await this.hoverNearResizer();
|
|
477
|
+
targetResizer = floatingResizer;
|
|
478
|
+
}
|
|
479
|
+
} catch (error) {
|
|
480
|
+
targetResizer = resizer;
|
|
481
|
+
}
|
|
482
|
+
const resizerBounds = await targetResizer.boundingBox();
|
|
483
|
+
if (!resizerBounds) {
|
|
484
|
+
throw new Error("Could not get resizer bounds for drag operation");
|
|
485
|
+
}
|
|
486
|
+
const startX = resizerBounds.x + resizerBounds.width / 2;
|
|
487
|
+
const startY = resizerBounds.y + resizerBounds.height / 2;
|
|
488
|
+
const endX = startX + deltaX;
|
|
489
|
+
const endY = startY + deltaY;
|
|
490
|
+
await this.page.mouse.move(startX, startY);
|
|
491
|
+
await this.page.mouse.down();
|
|
492
|
+
await this.page.mouse.move(endX, endY, { steps: 5 });
|
|
493
|
+
await this.page.mouse.up();
|
|
494
|
+
await this.page.waitForTimeout(100);
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Gets the primary panel element
|
|
498
|
+
*/
|
|
499
|
+
getPrimaryPanel() {
|
|
500
|
+
return this.locator.locator('[class*="primaryPanel"]').first();
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Gets the secondary panel element
|
|
504
|
+
*/
|
|
505
|
+
getSecondaryPanel() {
|
|
506
|
+
return this.locator.locator('[class*="secondaryPanel"]').first();
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
class ExpandableItemDriver extends ComponentDriver {
|
|
510
|
+
getSummary() {
|
|
511
|
+
return this.component.locator('[class*="_summary_"]');
|
|
512
|
+
}
|
|
513
|
+
getSummaryContent() {
|
|
514
|
+
return this.component.locator('[class*="_summaryContent_"]');
|
|
515
|
+
}
|
|
516
|
+
getContent() {
|
|
517
|
+
return this.component.locator('[class*="_content_"]');
|
|
518
|
+
}
|
|
519
|
+
getIcon() {
|
|
520
|
+
return this.component.locator('[class*="_icon_"] svg');
|
|
521
|
+
}
|
|
522
|
+
getSwitch() {
|
|
523
|
+
return this.component.getByRole("switch");
|
|
524
|
+
}
|
|
525
|
+
async isExpanded() {
|
|
526
|
+
return await this.component.locator('[class*="_content_"]').isVisible();
|
|
527
|
+
}
|
|
528
|
+
async isDisabled() {
|
|
529
|
+
return await this.component.evaluate((el) => el.className.includes("disabled"));
|
|
530
|
+
}
|
|
531
|
+
async expand() {
|
|
532
|
+
if (!await this.isExpanded()) {
|
|
533
|
+
await this.getSummary().click();
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
async collapse() {
|
|
537
|
+
if (await this.isExpanded()) {
|
|
538
|
+
await this.getSummary().click();
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
async toggle() {
|
|
542
|
+
await this.getSummary().click();
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
class FileInputDriver extends ComponentDriver {
|
|
546
|
+
getTextBox() {
|
|
547
|
+
return this.component.locator("input[readonly]");
|
|
548
|
+
}
|
|
549
|
+
getHiddenInput() {
|
|
550
|
+
return this.component.locator('input[type="file"]');
|
|
551
|
+
}
|
|
552
|
+
getBrowseButton() {
|
|
553
|
+
return this.component.locator('[class*="_button_"]');
|
|
554
|
+
}
|
|
555
|
+
getContainer() {
|
|
556
|
+
return this.component;
|
|
557
|
+
}
|
|
558
|
+
async isEnabled() {
|
|
559
|
+
const browseButton = this.getBrowseButton();
|
|
560
|
+
return !await browseButton.isDisabled();
|
|
561
|
+
}
|
|
562
|
+
async getSelectedFiles() {
|
|
563
|
+
const textBox = this.getTextBox();
|
|
564
|
+
const value = await textBox.inputValue();
|
|
565
|
+
return value || "";
|
|
566
|
+
}
|
|
567
|
+
async openFileDialog() {
|
|
568
|
+
await this.getBrowseButton().click();
|
|
569
|
+
}
|
|
570
|
+
async getPlaceholder() {
|
|
571
|
+
const textBox = this.getTextBox();
|
|
572
|
+
return await textBox.getAttribute("placeholder") || "";
|
|
573
|
+
}
|
|
574
|
+
async focusButton() {
|
|
575
|
+
await this.getBrowseButton().focus();
|
|
576
|
+
}
|
|
577
|
+
async hasReadOnlyAttribute() {
|
|
578
|
+
const textBox = this.getTextBox();
|
|
579
|
+
return await textBox.getAttribute("readonly") !== null;
|
|
580
|
+
}
|
|
581
|
+
async getAcceptedFileTypes() {
|
|
582
|
+
const hiddenInput = this.getHiddenInput();
|
|
583
|
+
return await hiddenInput.getAttribute("accept") || "";
|
|
584
|
+
}
|
|
585
|
+
async isMultiple() {
|
|
586
|
+
const hiddenInput = this.getHiddenInput();
|
|
587
|
+
return await hiddenInput.getAttribute("multiple") !== null;
|
|
588
|
+
}
|
|
589
|
+
async isDirectory() {
|
|
590
|
+
const hiddenInput = this.getHiddenInput();
|
|
591
|
+
return await hiddenInput.getAttribute("webkitdirectory") !== null;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
class FileUploadDropZoneDriver extends ComponentDriver {
|
|
595
|
+
getWrapper() {
|
|
596
|
+
return this.component.locator('[class*="_wrapper_"]');
|
|
597
|
+
}
|
|
598
|
+
getHiddenInput() {
|
|
599
|
+
return this.component.locator('input[type="file"]');
|
|
600
|
+
}
|
|
601
|
+
getDropPlaceholder() {
|
|
602
|
+
return this.component.locator('[class*="_dropPlaceholder_"]');
|
|
603
|
+
}
|
|
604
|
+
getDropIcon() {
|
|
605
|
+
return this.getDropPlaceholder().locator("svg");
|
|
606
|
+
}
|
|
607
|
+
async isDropPlaceholderVisible() {
|
|
608
|
+
return await this.getDropPlaceholder().isVisible();
|
|
609
|
+
}
|
|
610
|
+
async isEnabled() {
|
|
611
|
+
const input = this.getHiddenInput();
|
|
612
|
+
const isDisabled = await input.isDisabled();
|
|
613
|
+
return !isDisabled;
|
|
614
|
+
}
|
|
615
|
+
async getDropText() {
|
|
616
|
+
return await this.getDropPlaceholder().textContent() || "";
|
|
617
|
+
}
|
|
618
|
+
async triggerDragEnter() {
|
|
619
|
+
await this.component.dispatchEvent("dragenter");
|
|
620
|
+
}
|
|
621
|
+
async triggerDragLeave() {
|
|
622
|
+
await this.component.dispatchEvent("dragleave");
|
|
623
|
+
}
|
|
624
|
+
async triggerDrop(files = ["test.txt"]) {
|
|
625
|
+
const hiddenInput = this.getHiddenInput();
|
|
626
|
+
const fileObjects = files.map((name) => {
|
|
627
|
+
return {
|
|
628
|
+
name,
|
|
629
|
+
mimeType: "text/plain",
|
|
630
|
+
buffer: Buffer.from("test content")
|
|
631
|
+
};
|
|
632
|
+
});
|
|
633
|
+
await hiddenInput.setInputFiles(fileObjects);
|
|
634
|
+
await this.component.evaluate((element, fileNames) => {
|
|
635
|
+
const event = new DragEvent("drop", {
|
|
636
|
+
bubbles: true,
|
|
637
|
+
cancelable: true,
|
|
638
|
+
dataTransfer: new DataTransfer()
|
|
639
|
+
});
|
|
640
|
+
fileNames.forEach((fileName) => {
|
|
641
|
+
var _a2;
|
|
642
|
+
const file = new File(["test content"], fileName, { type: "text/plain" });
|
|
643
|
+
(_a2 = event.dataTransfer) == null ? void 0 : _a2.items.add(file);
|
|
644
|
+
});
|
|
645
|
+
element.dispatchEvent(event);
|
|
646
|
+
}, files);
|
|
647
|
+
}
|
|
648
|
+
async triggerPaste() {
|
|
649
|
+
await this.component.dispatchEvent("paste", {
|
|
650
|
+
clipboardData: {
|
|
651
|
+
files: [{ name: "pasted.txt", type: "text/plain", size: 50 }],
|
|
652
|
+
items: [{ kind: "file", getAsFile: () => ({ name: "pasted.txt" }) }]
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
async hasChildren() {
|
|
657
|
+
const childrenCount = await this.component.locator('> *:not(input):not([class*="_dropPlaceholder_"])').count();
|
|
658
|
+
return childrenCount > 0;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
class BackdropDriver extends ComponentDriver {
|
|
662
|
+
getBackdrop() {
|
|
663
|
+
return this.component.locator("> *").first();
|
|
664
|
+
}
|
|
665
|
+
getOverlay() {
|
|
666
|
+
return this.component.locator("> *").nth(1);
|
|
667
|
+
}
|
|
668
|
+
getDefaultBackgroundColor() {
|
|
669
|
+
return "rgb(0, 0, 0)";
|
|
670
|
+
}
|
|
671
|
+
getDefaultOpacity() {
|
|
672
|
+
return "0.1";
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
class FormDriver extends ComponentDriver {
|
|
676
|
+
async mockExternalApi(url, apiOptions) {
|
|
677
|
+
const { status = 200, headers = {}, body = {} } = apiOptions;
|
|
678
|
+
await this.page.route(
|
|
679
|
+
url,
|
|
680
|
+
(route) => route.fulfill({ status, headers, body: JSON.stringify(body) })
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
get submitButton() {
|
|
684
|
+
return this.getByPartName("submitButton");
|
|
685
|
+
}
|
|
686
|
+
get cancelButton() {
|
|
687
|
+
return this.getByPartName("cancelButton");
|
|
688
|
+
}
|
|
689
|
+
async hasSubmitButton() {
|
|
690
|
+
return await this.submitButton.count() > 0;
|
|
691
|
+
}
|
|
692
|
+
async submitForm(trigger = "click") {
|
|
693
|
+
if (trigger === "keypress") {
|
|
694
|
+
if (await this.hasSubmitButton() && await this.submitButton.isEnabled()) {
|
|
695
|
+
await this.submitButton.focus();
|
|
696
|
+
}
|
|
697
|
+
await this.locator.locator("input").waitFor();
|
|
698
|
+
const firstInputChild = this.locator.locator("input");
|
|
699
|
+
if (await firstInputChild.count() > 0) {
|
|
700
|
+
await firstInputChild.first().focus();
|
|
701
|
+
}
|
|
702
|
+
await this.page.keyboard.press("Enter");
|
|
703
|
+
} else if (trigger === "click") {
|
|
704
|
+
await this.submitButton.click();
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
async getSubmitRequest(endpoint = "/entities", requestMethod = "POST", trigger = "click", timeout = 5e3) {
|
|
708
|
+
const requestPromise = this.page.waitForRequest(
|
|
709
|
+
(request) => request.url().includes(endpoint) && request.method().toLowerCase() === requestMethod.toLowerCase(),
|
|
710
|
+
{ timeout }
|
|
711
|
+
);
|
|
712
|
+
await this.submitForm(trigger);
|
|
713
|
+
return requestPromise;
|
|
714
|
+
}
|
|
715
|
+
getSubmitResponse(endpoint = "/entities", responseStatus = 200, timeout = 5e3) {
|
|
716
|
+
const responsePromise = this.page.waitForResponse(
|
|
717
|
+
(response) => response.url().includes(endpoint) && response.status() === responseStatus,
|
|
718
|
+
{ timeout }
|
|
719
|
+
);
|
|
720
|
+
return responsePromise;
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Gets the validation summary component inside the Form.
|
|
724
|
+
* Uses the 'data-validation-summary' attribute to find the component
|
|
725
|
+
*/
|
|
726
|
+
getValidationSummary() {
|
|
727
|
+
return this.component.locator("[data-validation-summary='true']");
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Gets the validation display components inside the Form.
|
|
731
|
+
* Uses the 'data-validation-display-severity' attribute to find the components.
|
|
732
|
+
* The attribute contains the severity of the validation.
|
|
733
|
+
*/
|
|
734
|
+
getValidationDisplays() {
|
|
735
|
+
return this.component.locator("[data-validation-summary='true']").locator("[data-validation-display-severity]");
|
|
736
|
+
}
|
|
737
|
+
getValidationDisplaysBySeverity(severity) {
|
|
738
|
+
return this.component.locator("[data-validation-summary='true']").locator(`[data-validation-display-severity="${severity}"]`);
|
|
739
|
+
}
|
|
740
|
+
// TODO: it would be a nice to have features to get validation displays and map them automatically
|
|
741
|
+
/* async getValidationDisplaysAsDriver() {
|
|
742
|
+
const displays = await this.getValidationDisplays();
|
|
743
|
+
const displayList: ValidationDisplayDriver[] = [];
|
|
744
|
+
|
|
745
|
+
const displayNum = await displays.count();
|
|
746
|
+
for (let i = 0; i < displayNum; i++) {
|
|
747
|
+
const element = displays.nth(i);
|
|
748
|
+
displayList.push(new ValidationDisplayDriver({ locator: element, page: this.page }));
|
|
749
|
+
}
|
|
750
|
+
return displayList;
|
|
751
|
+
} */
|
|
752
|
+
}
|
|
753
|
+
class ValidationSummaryDriver extends ComponentDriver {
|
|
754
|
+
/**
|
|
755
|
+
* Gets the validation display components inside the Form.
|
|
756
|
+
* Uses the 'data-validation-display-severity' attribute to find the components.
|
|
757
|
+
* The attribute contains the severity of the validation.
|
|
758
|
+
*/
|
|
759
|
+
getValidationDisplays() {
|
|
760
|
+
return this.component.locator("[data-validation-summary='true']").locator("[data-validation-display-severity]");
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
class ValidationDisplayDriver extends ComponentDriver {
|
|
764
|
+
getSeverity() {
|
|
765
|
+
return this.component.getAttribute("data-validation-display-severity");
|
|
766
|
+
}
|
|
767
|
+
getText() {
|
|
768
|
+
return this.component.locator("li").textContent();
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
class MarkdownDriver extends ComponentDriver {
|
|
772
|
+
async hasHtmlElement(elements) {
|
|
773
|
+
const contents = await this.component.innerHTML();
|
|
774
|
+
elements = typeof elements === "string" ? [elements] : elements;
|
|
775
|
+
return elements.map((e) => `<${e}`).reduce((acc, curr) => acc && contents.includes(curr), true);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
class ItemsDriver extends ComponentDriver {
|
|
779
|
+
}
|
|
780
|
+
class RangeDriver extends ComponentDriver {
|
|
781
|
+
}
|
|
782
|
+
class DatePickerDriver extends ComponentDriver {
|
|
783
|
+
async toggleDropdownVisibility() {
|
|
784
|
+
await this.component.click();
|
|
785
|
+
}
|
|
786
|
+
async pickADay(value) {
|
|
787
|
+
await this.component.getByRole("gridcell", { name: value }).or(this.page.getByRole("gridcell", { name: value })).first().click({ force: true });
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
class AutoCompleteDriver extends ComponentDriver {
|
|
791
|
+
async toggleOptionsVisibility() {
|
|
792
|
+
await this.component.click();
|
|
793
|
+
}
|
|
794
|
+
async selectLabel(value) {
|
|
795
|
+
await this.component.getByRole("option", { name: value }).or(this.page.getByRole("option", { name: value })).first().click({ force: true });
|
|
796
|
+
}
|
|
797
|
+
async searchFor(value) {
|
|
798
|
+
await this.page.getByRole("combobox").fill(value);
|
|
799
|
+
}
|
|
800
|
+
async chooseIndex(index) {
|
|
801
|
+
await this.locator.getByRole("option").nth(index).or(this.page.getByRole("option").nth(index)).first().click();
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
class SelectDriver extends ComponentDriver {
|
|
805
|
+
async toggleOptionsVisibility() {
|
|
806
|
+
await this.component.click();
|
|
807
|
+
}
|
|
808
|
+
async selectLabel(value) {
|
|
809
|
+
await this.component.getByRole("option", { name: value }).or(this.page.getByRole("option", { name: value })).first().click({ force: true });
|
|
810
|
+
}
|
|
811
|
+
async selectFirstLabelPostSearh(label) {
|
|
812
|
+
await this.searchFor(label);
|
|
813
|
+
await this.chooseIndex(0);
|
|
814
|
+
}
|
|
815
|
+
async searchFor(value) {
|
|
816
|
+
await this.page.getByRole("searchbox").fill(value);
|
|
817
|
+
}
|
|
818
|
+
async chooseIndex(index) {
|
|
819
|
+
await this.locator.getByRole("option").nth(index).or(this.page.getByRole("option").nth(index)).first().click();
|
|
820
|
+
}
|
|
821
|
+
async selectMultipleLabels(values) {
|
|
822
|
+
for (const value of values) {
|
|
823
|
+
await this.component.getByRole("option", { name: value }).or(this.page.getByRole("option", { name: value })).first().click();
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
class RadioGroupDriver extends ComponentDriver {
|
|
828
|
+
}
|
|
829
|
+
class TextAreaDriver extends InputComponentDriver {
|
|
830
|
+
}
|
|
831
|
+
class ProgressBarDriver extends ComponentDriver {
|
|
832
|
+
get bar() {
|
|
833
|
+
return this.component.locator("> div");
|
|
834
|
+
}
|
|
835
|
+
async getProgressRatio() {
|
|
836
|
+
const style = await this.bar.getAttribute("style");
|
|
837
|
+
if (!style) return 0;
|
|
838
|
+
const widthMatch = style.match(/width:\s*(\d+(?:\.\d+)?)%/);
|
|
839
|
+
return widthMatch ? parseFloat(widthMatch[1]) / 100 : 0;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
class ListDriver extends ComponentDriver {
|
|
843
|
+
/**
|
|
844
|
+
* Gets all list item elements
|
|
845
|
+
*/
|
|
846
|
+
get items() {
|
|
847
|
+
return this.component.locator("[data-list-item]").or(this.component.locator("div").filter({ hasText: /^(?!.*Loading).*/ }));
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Gets the loading spinner element
|
|
851
|
+
*/
|
|
852
|
+
get loadingSpinner() {
|
|
853
|
+
return this.component.locator('[class*="loadingWrapper"]');
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Checks if the loading state is visible
|
|
857
|
+
*/
|
|
858
|
+
async isLoading() {
|
|
859
|
+
return await this.loadingSpinner.isVisible().catch(() => false);
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Gets group header elements
|
|
863
|
+
*/
|
|
864
|
+
get groupHeaders() {
|
|
865
|
+
return this.component.locator("[data-group-header]").or(this.component.locator("div").filter({ hasText: /^Category:|^Header:/ }));
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Gets group footer elements
|
|
869
|
+
*/
|
|
870
|
+
get groupFooters() {
|
|
871
|
+
return this.component.locator("[data-group-footer]").or(this.component.locator("div").filter({ hasText: /^End of/ }));
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Gets the empty state element
|
|
875
|
+
*/
|
|
876
|
+
get emptyState() {
|
|
877
|
+
return this.component.locator("[data-empty-state]").or(this.component.getByText("No items found!"));
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Scrolls the list to a specific position
|
|
881
|
+
*/
|
|
882
|
+
async scrollTo(position) {
|
|
883
|
+
if (position === "top") {
|
|
884
|
+
await this.component.evaluate((el) => el.scrollTo({ top: 0 }));
|
|
885
|
+
} else if (position === "bottom") {
|
|
886
|
+
await this.component.evaluate((el) => el.scrollTo({ top: el.scrollHeight }));
|
|
887
|
+
} else {
|
|
888
|
+
await this.component.evaluate((el, pos) => el.scrollTo({ top: pos }), position);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Gets the number of visible items (for virtualization testing)
|
|
893
|
+
*/
|
|
894
|
+
async getVisibleItemCount() {
|
|
895
|
+
return await this.items.count();
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Gets item at specific index
|
|
899
|
+
*/
|
|
900
|
+
getItemAt(index) {
|
|
901
|
+
return this.items.nth(index);
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Gets item by text content
|
|
905
|
+
*/
|
|
906
|
+
getItemByText(text) {
|
|
907
|
+
return this.component.getByText(text);
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Checks if list is empty
|
|
911
|
+
*/
|
|
912
|
+
async isEmpty() {
|
|
913
|
+
const noDataText = await this.component.textContent();
|
|
914
|
+
const hasNoDataMessage = (noDataText == null ? void 0 : noDataText.includes("No data available")) || (noDataText == null ? void 0 : noDataText.includes("No items found"));
|
|
915
|
+
const itemCount = await this.items.count();
|
|
916
|
+
const hasDataItems = itemCount > 0 && !hasNoDataMessage;
|
|
917
|
+
return hasNoDataMessage || !hasDataItems;
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Gets all text content from visible items
|
|
921
|
+
*/
|
|
922
|
+
async getVisibleItemTexts() {
|
|
923
|
+
const items = await this.items.all();
|
|
924
|
+
const texts = await Promise.all(items.map((item) => item.textContent()));
|
|
925
|
+
return texts.filter((text) => text !== null);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
class TextDriver extends ComponentDriver {
|
|
929
|
+
}
|
|
930
|
+
class HeadingDriver extends ComponentDriver {
|
|
931
|
+
}
|
|
932
|
+
class IconDriver extends ComponentDriver {
|
|
933
|
+
get svgIcon() {
|
|
934
|
+
return this.component.locator("svg");
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
class StackDriver extends ComponentDriver {
|
|
938
|
+
}
|
|
939
|
+
class HStackDriver extends StackDriver {
|
|
940
|
+
}
|
|
941
|
+
class VStackDriver extends StackDriver {
|
|
942
|
+
}
|
|
943
|
+
class LinkDriver extends ComponentDriver {
|
|
944
|
+
}
|
|
945
|
+
class NavLinkDriver extends ComponentDriver {
|
|
946
|
+
}
|
|
947
|
+
class NavGroupDriver extends ComponentDriver {
|
|
948
|
+
getIcons() {
|
|
949
|
+
return this.locator.locator("> svg").or(this.locator.locator("> img"));
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
class NavPanelDriver extends ComponentDriver {
|
|
953
|
+
}
|
|
954
|
+
class CardDriver extends ComponentDriver {
|
|
955
|
+
get avatar() {
|
|
956
|
+
return this.component.getByRole("img", { name: "avatar" });
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
class AccordionDriver extends ComponentDriver {
|
|
960
|
+
}
|
|
961
|
+
class AppHeaderDriver extends ComponentDriver {
|
|
962
|
+
}
|
|
963
|
+
class AppFooterDriver extends ComponentDriver {
|
|
964
|
+
}
|
|
965
|
+
class BadgeDriver extends ComponentDriver {
|
|
966
|
+
}
|
|
967
|
+
class NoResultDriver extends ComponentDriver {
|
|
968
|
+
}
|
|
969
|
+
class OptionDriver extends ComponentDriver {
|
|
970
|
+
}
|
|
971
|
+
class FormItemDriver extends ComponentDriver {
|
|
972
|
+
get input() {
|
|
973
|
+
return this.component.locator(">input").or(this.component).first();
|
|
974
|
+
}
|
|
975
|
+
get textBox() {
|
|
976
|
+
return this.input.getByRole("textbox");
|
|
977
|
+
}
|
|
978
|
+
get checkbox() {
|
|
979
|
+
return this.input.getByRole("checkbox");
|
|
980
|
+
}
|
|
981
|
+
get label() {
|
|
982
|
+
return this.component.locator("label");
|
|
983
|
+
}
|
|
984
|
+
get validationStatusTag() {
|
|
985
|
+
return "data-validation-status";
|
|
986
|
+
}
|
|
987
|
+
get validationStatusIndicator() {
|
|
988
|
+
return this.component.locator(`[${this.validationStatusTag}]`);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
class HtmlTagDriver extends ComponentDriver {
|
|
992
|
+
}
|
|
993
|
+
class CodeBlockDriver extends ComponentDriver {
|
|
994
|
+
getHeader() {
|
|
995
|
+
return this.component.locator('[class*="codeBlockHeader"]');
|
|
996
|
+
}
|
|
997
|
+
getContent() {
|
|
998
|
+
return this.component.locator('[class*="codeBlockContent"]');
|
|
999
|
+
}
|
|
1000
|
+
getCopyButton() {
|
|
1001
|
+
return this.component.locator('[class*="codeBlockCopyButton"] button');
|
|
1002
|
+
}
|
|
1003
|
+
getFilename() {
|
|
1004
|
+
return this.getHeader().locator("span");
|
|
1005
|
+
}
|
|
1006
|
+
async isCopyButtonVisible() {
|
|
1007
|
+
return await this.getCopyButton().isVisible();
|
|
1008
|
+
}
|
|
1009
|
+
async hasHeader() {
|
|
1010
|
+
return await this.getHeader().isVisible();
|
|
1011
|
+
}
|
|
1012
|
+
async getCodeText() {
|
|
1013
|
+
return await this.getContent().textContent();
|
|
1014
|
+
}
|
|
1015
|
+
async clickCopyButton() {
|
|
1016
|
+
await this.getCopyButton().click();
|
|
1017
|
+
}
|
|
1018
|
+
async hoverContent() {
|
|
1019
|
+
await this.getContent().hover();
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
class CheckboxDriver extends InputComponentDriver {
|
|
1023
|
+
async getIndicatorColor() {
|
|
1024
|
+
const specifier = this.component.getByRole("checkbox").or(this.component).last();
|
|
1025
|
+
const { boxShadow } = await getPseudoStyles(specifier, "::before", "box-shadow");
|
|
1026
|
+
const colorMatch = boxShadow.match(/(rgba?\([^)]+\)|hsla?\([^)]+\)|#[a-fA-F0-9]{3,8})/);
|
|
1027
|
+
return colorMatch ? colorMatch[1] : null;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
class LabelDriver extends ComponentDriver {
|
|
1031
|
+
}
|
|
1032
|
+
class SpinnerDriver extends ComponentDriver {
|
|
1033
|
+
/**
|
|
1034
|
+
* Gets the main spinner element (the one with the ring animation)
|
|
1035
|
+
*/
|
|
1036
|
+
get spinnerElement() {
|
|
1037
|
+
return this.page.locator('[data-testid="test-id-component"]').filter({ has: this.page.locator("div") }).first();
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Gets a specific spinner element by index (for multiple spinners)
|
|
1041
|
+
*/
|
|
1042
|
+
getSpinnerByIndex(index) {
|
|
1043
|
+
return this.page.locator('[data-testid="test-id-component"]').filter({ has: this.page.locator("div") }).nth(index);
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Gets the fullscreen wrapper element (only exists when fullScreen=true)
|
|
1047
|
+
*/
|
|
1048
|
+
get fullScreenWrapper() {
|
|
1049
|
+
return this.page.locator('div[class*="_fullScreenSpinnerWrapper_"]');
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Checks if the spinner is in fullscreen mode
|
|
1053
|
+
*/
|
|
1054
|
+
async isFullScreen() {
|
|
1055
|
+
try {
|
|
1056
|
+
const wrapper = this.fullScreenWrapper;
|
|
1057
|
+
return await wrapper.isVisible();
|
|
1058
|
+
} catch {
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Gets the computed style of the spinner element
|
|
1064
|
+
*/
|
|
1065
|
+
async getSpinnerStyle() {
|
|
1066
|
+
const element = this.spinnerElement;
|
|
1067
|
+
return await element.evaluate((el) => {
|
|
1068
|
+
const styles = window.getComputedStyle(el);
|
|
1069
|
+
return {
|
|
1070
|
+
display: styles.display,
|
|
1071
|
+
position: styles.position,
|
|
1072
|
+
width: styles.width,
|
|
1073
|
+
height: styles.height,
|
|
1074
|
+
animationDuration: styles.animationDuration,
|
|
1075
|
+
className: el.className
|
|
1076
|
+
};
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Gets the animation duration from the child elements (where the actual animation occurs)
|
|
1081
|
+
*/
|
|
1082
|
+
async getAnimationDuration() {
|
|
1083
|
+
const element = this.spinnerElement;
|
|
1084
|
+
return await element.evaluate((el) => {
|
|
1085
|
+
const firstChild = el.querySelector("div");
|
|
1086
|
+
if (firstChild) {
|
|
1087
|
+
const styles = window.getComputedStyle(firstChild);
|
|
1088
|
+
return styles.animationDuration;
|
|
1089
|
+
}
|
|
1090
|
+
return "0s";
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Waits for the spinner to appear
|
|
1095
|
+
*/
|
|
1096
|
+
async waitForSpinner(timeout = 5e3) {
|
|
1097
|
+
await this.spinnerElement.waitFor({ state: "visible", timeout });
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Waits for the spinner to disappear
|
|
1101
|
+
*/
|
|
1102
|
+
async waitForSpinnerToDisappear(timeout = 5e3) {
|
|
1103
|
+
await this.spinnerElement.waitFor({ state: "hidden", timeout });
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Checks if the spinner is visible
|
|
1107
|
+
*/
|
|
1108
|
+
async isVisible() {
|
|
1109
|
+
return await this.spinnerElement.isVisible();
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Gets the CSS class name to verify CSS modules are working
|
|
1113
|
+
*/
|
|
1114
|
+
async getClassName() {
|
|
1115
|
+
return await this.spinnerElement.getAttribute("class") || "";
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Gets the number of child div elements (should be 4 for the ring animation)
|
|
1119
|
+
*/
|
|
1120
|
+
async getChildCount() {
|
|
1121
|
+
return await this.spinnerElement.evaluate((el) => {
|
|
1122
|
+
return el.querySelectorAll("div").length;
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Gets the total number of spinner components on the page
|
|
1127
|
+
*/
|
|
1128
|
+
async getSpinnerCount() {
|
|
1129
|
+
return await this.page.locator('[data-testid="test-id-component"]').count();
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Gets a spinner by specific test ID
|
|
1133
|
+
*/
|
|
1134
|
+
getSpinnerByTestId(testId) {
|
|
1135
|
+
return this.page.locator(`[data-testid="${testId}"]`);
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Checks if fullscreen mode has the correct wrapper structure
|
|
1139
|
+
*/
|
|
1140
|
+
async getFullScreenWrapperInfo() {
|
|
1141
|
+
const wrapper = this.fullScreenWrapper;
|
|
1142
|
+
if (!await wrapper.isVisible()) {
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1145
|
+
return await wrapper.evaluate((el) => {
|
|
1146
|
+
const parent = el.parentElement;
|
|
1147
|
+
const styles = window.getComputedStyle(el);
|
|
1148
|
+
return {
|
|
1149
|
+
position: styles.position,
|
|
1150
|
+
inset: styles.inset,
|
|
1151
|
+
parentClassName: (parent == null ? void 0 : parent.className) || "",
|
|
1152
|
+
hasSpinnerChild: !!el.querySelector('[class*="_lds-ring_"]')
|
|
1153
|
+
};
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
class DropdownMenuDriver extends ComponentDriver {
|
|
1158
|
+
/**
|
|
1159
|
+
* Get the trigger button element
|
|
1160
|
+
* For DropdownMenu, we'll look for the button on the page level since Radix UI may render it separately
|
|
1161
|
+
*/
|
|
1162
|
+
getTrigger() {
|
|
1163
|
+
return this.page.getByRole("button").first();
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Open the dropdown menu
|
|
1167
|
+
*/
|
|
1168
|
+
async open() {
|
|
1169
|
+
const trigger = this.getTrigger();
|
|
1170
|
+
await trigger.click();
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Close the dropdown menu by clicking outside
|
|
1174
|
+
*/
|
|
1175
|
+
async close() {
|
|
1176
|
+
try {
|
|
1177
|
+
await this.page.keyboard.press("Escape");
|
|
1178
|
+
} catch {
|
|
1179
|
+
await this.page.click("html");
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Get all menu items
|
|
1184
|
+
*/
|
|
1185
|
+
getMenuItems() {
|
|
1186
|
+
return this.page.getByRole("menuitem");
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* Get a specific menu item by text
|
|
1190
|
+
*/
|
|
1191
|
+
getMenuItem(text) {
|
|
1192
|
+
return this.page.getByRole("menuitem", { name: text });
|
|
1193
|
+
}
|
|
1194
|
+
/**
|
|
1195
|
+
* Click a menu item by text
|
|
1196
|
+
*/
|
|
1197
|
+
async clickMenuItem(text) {
|
|
1198
|
+
await this.getMenuItem(text).click();
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Get submenu items
|
|
1202
|
+
*/
|
|
1203
|
+
getSubMenuItems(parentText) {
|
|
1204
|
+
return this.page.getByText(parentText);
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Open a submenu by hovering over it
|
|
1208
|
+
*/
|
|
1209
|
+
async openSubMenu(submenuText) {
|
|
1210
|
+
await this.page.getByText(submenuText).hover();
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Get menu separators
|
|
1214
|
+
*/
|
|
1215
|
+
getMenuSeparators() {
|
|
1216
|
+
return this.page.locator('[class*="DropdownMenuSeparator"]');
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Get the menu content container
|
|
1220
|
+
*/
|
|
1221
|
+
getMenuContent() {
|
|
1222
|
+
return this.page.locator('[class*="DropdownMenuContent"]');
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Check if the menu is open
|
|
1226
|
+
*/
|
|
1227
|
+
async isOpen() {
|
|
1228
|
+
try {
|
|
1229
|
+
const content = this.getMenuContent();
|
|
1230
|
+
return await content.isVisible();
|
|
1231
|
+
} catch {
|
|
1232
|
+
return false;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Wait for menu to open
|
|
1237
|
+
*/
|
|
1238
|
+
async waitForOpen() {
|
|
1239
|
+
await this.getMenuContent().waitFor({ state: "visible" });
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Wait for menu to close
|
|
1243
|
+
*/
|
|
1244
|
+
async waitForClose() {
|
|
1245
|
+
await this.getMenuContent().waitFor({ state: "hidden" });
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
class SliderDriver extends ComponentDriver {
|
|
1249
|
+
async getActiveThumb(thumbNumber = 0) {
|
|
1250
|
+
const thumbs = this.page.getByRole("slider");
|
|
1251
|
+
const thumbCount = await thumbs.count();
|
|
1252
|
+
if (thumbCount === 0) {
|
|
1253
|
+
throw new Error("No slider thumb found to drag");
|
|
1254
|
+
}
|
|
1255
|
+
if (thumbNumber < 0) {
|
|
1256
|
+
thumbNumber = 0;
|
|
1257
|
+
} else if (thumbNumber >= thumbCount) {
|
|
1258
|
+
thumbNumber = thumbCount - 1;
|
|
1259
|
+
}
|
|
1260
|
+
return thumbs.nth(thumbNumber);
|
|
1261
|
+
}
|
|
1262
|
+
async dragThumbByMouse(location, thumbNumber = 0) {
|
|
1263
|
+
const track = this.page.locator("[data-track]");
|
|
1264
|
+
await track.waitFor({ state: "visible" });
|
|
1265
|
+
const activeThumb = await this.getActiveThumb(thumbNumber);
|
|
1266
|
+
await activeThumb.waitFor({ state: "visible" });
|
|
1267
|
+
const thumbBox = await activeThumb.boundingBox();
|
|
1268
|
+
if (!thumbBox) {
|
|
1269
|
+
throw new Error("Could not get thumb bounding box");
|
|
1270
|
+
}
|
|
1271
|
+
const trackBox = await track.boundingBox();
|
|
1272
|
+
if (!trackBox) {
|
|
1273
|
+
throw new Error("Could not get track bounding box");
|
|
1274
|
+
}
|
|
1275
|
+
let targetX;
|
|
1276
|
+
if (location === "start") {
|
|
1277
|
+
targetX = trackBox.x;
|
|
1278
|
+
} else if (location === "end") {
|
|
1279
|
+
targetX = trackBox.x + trackBox.width;
|
|
1280
|
+
} else {
|
|
1281
|
+
targetX = trackBox.x + trackBox.width / 2;
|
|
1282
|
+
}
|
|
1283
|
+
const targetY = trackBox.y + trackBox.height / 2;
|
|
1284
|
+
await activeThumb.hover();
|
|
1285
|
+
await this.page.mouse.down({ button: "left" });
|
|
1286
|
+
await this.page.mouse.move(targetX, targetY);
|
|
1287
|
+
await this.page.mouse.up();
|
|
1288
|
+
}
|
|
1289
|
+
async stepThumbByKeyboard(key, thumbNumber = 0, repeat = 1) {
|
|
1290
|
+
const activeThumb = await this.getActiveThumb(thumbNumber);
|
|
1291
|
+
await activeThumb.focus();
|
|
1292
|
+
for (let i = 0; i < repeat; i++) {
|
|
1293
|
+
await this.page.keyboard.press(key);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
class TimeInputDriver extends InputComponentDriver {
|
|
1298
|
+
get hourInput() {
|
|
1299
|
+
return this.getByPartName("hour");
|
|
1300
|
+
}
|
|
1301
|
+
get minuteInput() {
|
|
1302
|
+
return this.getByPartName("minute");
|
|
1303
|
+
}
|
|
1304
|
+
get secondInput() {
|
|
1305
|
+
return this.getByPartName("second");
|
|
1306
|
+
}
|
|
1307
|
+
get amPmInput() {
|
|
1308
|
+
return this.getByPartName("ampm");
|
|
1309
|
+
}
|
|
1310
|
+
get clearButton() {
|
|
1311
|
+
return this.getByPartName("clearButton");
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
class TimerDriver extends ComponentDriver {
|
|
1315
|
+
async isEnabled() {
|
|
1316
|
+
const enabled = await this.component.getAttribute("data-timer-enabled");
|
|
1317
|
+
return enabled === "true";
|
|
1318
|
+
}
|
|
1319
|
+
async isRunning() {
|
|
1320
|
+
const running = await this.component.getAttribute("data-timer-running");
|
|
1321
|
+
return running === "true";
|
|
1322
|
+
}
|
|
1323
|
+
async isPaused() {
|
|
1324
|
+
const paused = await this.component.getAttribute("data-timer-paused");
|
|
1325
|
+
return paused === "true";
|
|
1326
|
+
}
|
|
1327
|
+
async isInInitialDelay() {
|
|
1328
|
+
const inDelay = await this.component.getAttribute("data-timer-in-initial-delay");
|
|
1329
|
+
return inDelay === "true";
|
|
1330
|
+
}
|
|
1331
|
+
async getInterval() {
|
|
1332
|
+
const interval = await this.component.getAttribute("data-timer-interval");
|
|
1333
|
+
return parseInt(interval || "0", 10);
|
|
1334
|
+
}
|
|
1335
|
+
async getInitialDelay() {
|
|
1336
|
+
const delay = await this.component.getAttribute("data-timer-initial-delay");
|
|
1337
|
+
return parseInt(delay || "0", 10);
|
|
1338
|
+
}
|
|
1339
|
+
async isOnce() {
|
|
1340
|
+
const once = await this.component.getAttribute("data-timer-once");
|
|
1341
|
+
return once === "true";
|
|
1342
|
+
}
|
|
1343
|
+
async hasExecuted() {
|
|
1344
|
+
const executed = await this.component.getAttribute("data-timer-has-executed");
|
|
1345
|
+
return executed === "true";
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
class DateInputDriver extends InputComponentDriver {
|
|
1349
|
+
get dayInput() {
|
|
1350
|
+
return this.getByPartName("day");
|
|
1351
|
+
}
|
|
1352
|
+
get monthInput() {
|
|
1353
|
+
return this.getByPartName("month");
|
|
1354
|
+
}
|
|
1355
|
+
get yearInput() {
|
|
1356
|
+
return this.getByPartName("year");
|
|
1357
|
+
}
|
|
1358
|
+
get clearButton() {
|
|
1359
|
+
return this.getByPartName("clearButton");
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
class ModalDialogDriver extends ComponentDriver {
|
|
1363
|
+
get titlePart() {
|
|
1364
|
+
return this.getByPartName("title");
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
class TextBoxDriver extends InputComponentDriver {
|
|
1368
|
+
get input() {
|
|
1369
|
+
return this.getByPartName("input");
|
|
1370
|
+
}
|
|
1371
|
+
get startAdornment() {
|
|
1372
|
+
return this.getByPartName(PART_START_ADORNMENT);
|
|
1373
|
+
}
|
|
1374
|
+
get endAdornment() {
|
|
1375
|
+
return this.getByPartName(PART_END_ADORNMENT);
|
|
1376
|
+
}
|
|
1377
|
+
get button() {
|
|
1378
|
+
return this.getByPartName(PART_END_ADORNMENT);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
const PART_SPINNER_UP = "spinnerUp";
|
|
1382
|
+
const PART_SPINNER_DOWN = "spinnerDown";
|
|
1383
|
+
class NumberBoxDriver extends InputComponentDriver {
|
|
1384
|
+
get input() {
|
|
1385
|
+
return this.getByPartName(PART_INPUT);
|
|
1386
|
+
}
|
|
1387
|
+
get startAdornment() {
|
|
1388
|
+
return this.getByPartName(PART_START_ADORNMENT);
|
|
1389
|
+
}
|
|
1390
|
+
get endAdornment() {
|
|
1391
|
+
return this.getByPartName(PART_END_ADORNMENT);
|
|
1392
|
+
}
|
|
1393
|
+
get spinnerUp() {
|
|
1394
|
+
return this.getByPartName(PART_SPINNER_UP);
|
|
1395
|
+
}
|
|
1396
|
+
get spinnerDown() {
|
|
1397
|
+
return this.getByPartName(PART_SPINNER_DOWN);
|
|
1398
|
+
}
|
|
1399
|
+
async increment() {
|
|
1400
|
+
await this.spinnerUp.click({ force: true });
|
|
1401
|
+
}
|
|
1402
|
+
async decrement() {
|
|
1403
|
+
await this.spinnerDown.click({ force: true });
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
class TreeDriver extends ComponentDriver {
|
|
1407
|
+
getNodeById(nodeId) {
|
|
1408
|
+
return this.component.locator(`[data-tree-node-id="${nodeId}"]`).first();
|
|
1409
|
+
}
|
|
1410
|
+
getNodeWrapperByTestId(marker) {
|
|
1411
|
+
return this.getByTestId(marker).locator("../..");
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
const expect = expect$1.extend({
|
|
1415
|
+
/**
|
|
1416
|
+
* Asserts whether the Button component has the expected label either through the `label` property
|
|
1417
|
+
* or as a text node child.
|
|
1418
|
+
*
|
|
1419
|
+
* **Usage**
|
|
1420
|
+
*
|
|
1421
|
+
* ```js
|
|
1422
|
+
* await initTestBed(`<Button label="hello" />`);
|
|
1423
|
+
* const driver = await createButtonDriver();
|
|
1424
|
+
* await expect(driver.component).toHaveLabel("hello"); // <- resolves to true
|
|
1425
|
+
* ```
|
|
1426
|
+
*
|
|
1427
|
+
* @param expected Expected string label
|
|
1428
|
+
*/
|
|
1429
|
+
async toHaveExplicitLabel(locator, expected) {
|
|
1430
|
+
const assertionName = "toHaveExplicitLabel";
|
|
1431
|
+
let pass = false;
|
|
1432
|
+
const label = await locator.evaluate(
|
|
1433
|
+
(element) => {
|
|
1434
|
+
var _a2;
|
|
1435
|
+
return (_a2 = [...element.childNodes].filter((e) => e.nodeType === Node.TEXT_NODE && e.textContent.trim()).map((e) => e.textContent.trim())) == null ? void 0 : _a2[0];
|
|
1436
|
+
}
|
|
1437
|
+
);
|
|
1438
|
+
if (label === expected) {
|
|
1439
|
+
pass = true;
|
|
1440
|
+
}
|
|
1441
|
+
const message = pass ? () => this.utils.matcherHint(assertionName, locator, expected, { isNot: this.isNot }) + `
|
|
1442
|
+
|
|
1443
|
+
Expected: ${this.isNot ? "not" : ""}${this.utils.printExpected(expected)}
|
|
1444
|
+
Received: ${this.utils.printReceived(label)}` : () => this.utils.matcherHint(assertionName, locator, expected, { isNot: this.isNot }) + `
|
|
1445
|
+
|
|
1446
|
+
Expected: ${this.utils.printExpected(expected)}
|
|
1447
|
+
Received: ${this.utils.printReceived(label)}`;
|
|
1448
|
+
return {
|
|
1449
|
+
message,
|
|
1450
|
+
pass,
|
|
1451
|
+
name: assertionName,
|
|
1452
|
+
expected,
|
|
1453
|
+
actual: void 0
|
|
1454
|
+
};
|
|
1455
|
+
},
|
|
1456
|
+
/**
|
|
1457
|
+
* Compares two numbers with an optional tolerance value. If the tolerance is set to 0 the comparator acts as `toEqual`.
|
|
1458
|
+
* Used to compare element dimensions on different platforms because of half pixel rendering.
|
|
1459
|
+
*
|
|
1460
|
+
* **Usage**
|
|
1461
|
+
*
|
|
1462
|
+
* ```js
|
|
1463
|
+
* const value = 8;
|
|
1464
|
+
* expect(value).toEqualWithTolerance(10, 2); // true
|
|
1465
|
+
* ```
|
|
1466
|
+
*
|
|
1467
|
+
* @param expected Expected value
|
|
1468
|
+
* @param tolerance Tolerance value, **default is 1**
|
|
1469
|
+
*/
|
|
1470
|
+
toEqualWithTolerance(provided, expected, tolerance = 1) {
|
|
1471
|
+
const assertionName = "toEqualWithTolerance";
|
|
1472
|
+
let pass = false;
|
|
1473
|
+
if (provided >= expected - (tolerance || 0) && provided <= expected + (tolerance || 0)) {
|
|
1474
|
+
pass = true;
|
|
1475
|
+
}
|
|
1476
|
+
const message = pass ? () => this.utils.matcherHint(assertionName, provided, expected, { isNot: this.isNot }) + `
|
|
1477
|
+
|
|
1478
|
+
Expected: ${this.isNot ? "not" : ""}${this.utils.printExpected(expected)}
|
|
1479
|
+
Received: ${this.utils.printReceived(provided)}` : () => this.utils.matcherHint(assertionName, provided, expected, { isNot: this.isNot }) + `
|
|
1480
|
+
|
|
1481
|
+
Expected: ${this.utils.printExpected(expected)}
|
|
1482
|
+
Received: ${this.utils.printReceived(provided)}`;
|
|
1483
|
+
return {
|
|
1484
|
+
message,
|
|
1485
|
+
pass,
|
|
1486
|
+
name: assertionName,
|
|
1487
|
+
expected,
|
|
1488
|
+
actual: void 0
|
|
1489
|
+
};
|
|
1490
|
+
},
|
|
1491
|
+
// ---
|
|
1492
|
+
// --- NOTE: Assertations below this line are experimental and are reserved for future test cases (ex. comparison tests)
|
|
1493
|
+
// ---
|
|
1494
|
+
/**
|
|
1495
|
+
* Asserts whether a component has the correct CSS `border-color` styling.
|
|
1496
|
+
*
|
|
1497
|
+
* @param expected Expected CSS color
|
|
1498
|
+
* @param border Border side(s), Default: **"all"**
|
|
1499
|
+
*
|
|
1500
|
+
* ---
|
|
1501
|
+
* **Usage**
|
|
1502
|
+
*
|
|
1503
|
+
* ```js
|
|
1504
|
+
* await initTestBed(`<Button label="hello" />`, {
|
|
1505
|
+
* testThemeVars: { "borderLeftColor-Button": "rgb(0, 0, 0)" },
|
|
1506
|
+
* });
|
|
1507
|
+
* const driver = await createButtonDriver();
|
|
1508
|
+
* await expect(driver.component).toHaveBorderColor("rgb(0, 0, 0)", "left");
|
|
1509
|
+
* ```
|
|
1510
|
+
*/
|
|
1511
|
+
async toHaveBorderColor(locator, expected, border = "all") {
|
|
1512
|
+
const assertionName = "toHaveBorder";
|
|
1513
|
+
let expectedBorderColor;
|
|
1514
|
+
try {
|
|
1515
|
+
expectedBorderColor = parseAsCSSColor(expected);
|
|
1516
|
+
if (Object.values(expectedBorderColor).length === 0) {
|
|
1517
|
+
throw new Error("Empty color!");
|
|
1518
|
+
}
|
|
1519
|
+
} catch (e) {
|
|
1520
|
+
return {
|
|
1521
|
+
message: () => "In " + this.utils.matcherHint(assertionName, locator, expected, { isNot: this.isNot }) + ":\n" + (e instanceof Error ? e.message : String(e)) + `
|
|
1522
|
+
|
|
1523
|
+
Expected: Correct CSS border color to expect (e.g. "red", "rgb(0, 0, 0)", "#AA0011")
|
|
1524
|
+
Received: ${this.utils.printReceived(expected)}`,
|
|
1525
|
+
pass: false,
|
|
1526
|
+
name: assertionName,
|
|
1527
|
+
expected,
|
|
1528
|
+
actual: void 0
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
let _border = [];
|
|
1532
|
+
if (typeof border === "string") {
|
|
1533
|
+
if (border === "all") {
|
|
1534
|
+
_border = ["top", "bottom", "left", "right"];
|
|
1535
|
+
} else {
|
|
1536
|
+
_border = [border];
|
|
1537
|
+
}
|
|
1538
|
+
} else {
|
|
1539
|
+
_border = Array.from(new Set(border)).filter(isBorderSide);
|
|
1540
|
+
}
|
|
1541
|
+
let pass = true;
|
|
1542
|
+
let matcherResult;
|
|
1543
|
+
const colorStr = `rgb(${expectedBorderColor.rgb().join(", ")})`;
|
|
1544
|
+
try {
|
|
1545
|
+
for (let b of _border) {
|
|
1546
|
+
await expect$1(locator).toHaveCSS(`border-${b}-color`, colorStr);
|
|
1547
|
+
}
|
|
1548
|
+
} catch (e) {
|
|
1549
|
+
matcherResult = e.matcherResult;
|
|
1550
|
+
pass = false;
|
|
1551
|
+
}
|
|
1552
|
+
const message = pass ? () => this.utils.matcherHint(assertionName, void 0, void 0, { isNot: this.isNot }) + `
|
|
1553
|
+
|
|
1554
|
+
Expected: not ${this.utils.printExpected(expected)}
|
|
1555
|
+
` + (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : "") : () => this.utils.matcherHint(assertionName, void 0, void 0, { isNot: this.isNot }) + `
|
|
1556
|
+
|
|
1557
|
+
Expected: ${this.utils.printExpected(expected)}
|
|
1558
|
+
` + (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : "");
|
|
1559
|
+
return {
|
|
1560
|
+
message,
|
|
1561
|
+
pass,
|
|
1562
|
+
name: assertionName,
|
|
1563
|
+
expected,
|
|
1564
|
+
actual: matcherResult == null ? void 0 : matcherResult.actual
|
|
1565
|
+
};
|
|
1566
|
+
},
|
|
1567
|
+
/**
|
|
1568
|
+
* Asserts whether a component has the correct CSS `border-width` styling.
|
|
1569
|
+
*
|
|
1570
|
+
* @param expected Expected CSS size
|
|
1571
|
+
* @param border Border side(s), Default: **"all"**
|
|
1572
|
+
*
|
|
1573
|
+
* ---
|
|
1574
|
+
* **Usage**
|
|
1575
|
+
*
|
|
1576
|
+
* ```js
|
|
1577
|
+
* await initTestBed(`<Button label="hello" />`, {
|
|
1578
|
+
* testThemeVars: { "borderLeftWidth-Button": "12px" },
|
|
1579
|
+
* });
|
|
1580
|
+
* const driver = await createButtonDriver();
|
|
1581
|
+
* await expect(driver.component).toHaveBorderWidth("12px", "left");
|
|
1582
|
+
* ```
|
|
1583
|
+
*/
|
|
1584
|
+
async toHaveBorderWidth(locator, expected, border = "all") {
|
|
1585
|
+
const assertionName = "toHaveBorder";
|
|
1586
|
+
let expectedBorderWidth;
|
|
1587
|
+
try {
|
|
1588
|
+
expectedBorderWidth = parseAsNumericCss(expected);
|
|
1589
|
+
if (Object.values(expectedBorderWidth).length === 0) {
|
|
1590
|
+
throw new Error("Empty width!");
|
|
1591
|
+
}
|
|
1592
|
+
} catch (e) {
|
|
1593
|
+
return {
|
|
1594
|
+
message: () => "In " + this.utils.matcherHint(assertionName, locator, expected, { isNot: this.isNot }) + ":\n" + (e instanceof Error ? e.message : String(e)) + `
|
|
1595
|
+
|
|
1596
|
+
Expected: Correct CSS border width to expect (e.g. "1px", "2rem")
|
|
1597
|
+
Received: ${this.utils.printReceived(expected)}`,
|
|
1598
|
+
pass: false,
|
|
1599
|
+
name: assertionName,
|
|
1600
|
+
expected,
|
|
1601
|
+
actual: void 0
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
let _border = [];
|
|
1605
|
+
if (typeof border === "string") {
|
|
1606
|
+
if (border === "all") {
|
|
1607
|
+
_border = ["top", "bottom", "left", "right"];
|
|
1608
|
+
} else {
|
|
1609
|
+
_border = [border];
|
|
1610
|
+
}
|
|
1611
|
+
} else {
|
|
1612
|
+
_border = Array.from(new Set(border)).filter(isBorderSide);
|
|
1613
|
+
}
|
|
1614
|
+
let pass = true;
|
|
1615
|
+
let matcherResult;
|
|
1616
|
+
const widthStr = numericCSSToString(expectedBorderWidth);
|
|
1617
|
+
try {
|
|
1618
|
+
for (let b of _border) {
|
|
1619
|
+
await expect$1(locator).toHaveCSS(`border-${b}-width`, widthStr);
|
|
1620
|
+
}
|
|
1621
|
+
} catch (e) {
|
|
1622
|
+
matcherResult = e.matcherResult;
|
|
1623
|
+
pass = false;
|
|
1624
|
+
}
|
|
1625
|
+
const message = pass ? () => this.utils.matcherHint(assertionName, void 0, void 0, { isNot: this.isNot }) + `
|
|
1626
|
+
|
|
1627
|
+
Expected: not ${this.utils.printExpected(expected)}
|
|
1628
|
+
` + (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : "") : () => this.utils.matcherHint(assertionName, void 0, void 0, { isNot: this.isNot }) + `
|
|
1629
|
+
|
|
1630
|
+
Expected: ${this.utils.printExpected(expected)}
|
|
1631
|
+
` + (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : "");
|
|
1632
|
+
return {
|
|
1633
|
+
message,
|
|
1634
|
+
pass,
|
|
1635
|
+
name: assertionName,
|
|
1636
|
+
expected,
|
|
1637
|
+
actual: matcherResult == null ? void 0 : matcherResult.actual
|
|
1638
|
+
};
|
|
1639
|
+
},
|
|
1640
|
+
/**
|
|
1641
|
+
* Asserts whether a component has the correct CSS `border-style` styling.
|
|
1642
|
+
*
|
|
1643
|
+
* @param expected Expected CSS border style
|
|
1644
|
+
* @param border Border side(s), default: **"all"**
|
|
1645
|
+
*
|
|
1646
|
+
* ---
|
|
1647
|
+
* **Usage**
|
|
1648
|
+
*
|
|
1649
|
+
* ```js
|
|
1650
|
+
* await initTestBed(`<Button label="hello" />`, {
|
|
1651
|
+
* testThemeVars: { "borderLeftStyle-Button": "dotted" },
|
|
1652
|
+
* });
|
|
1653
|
+
* const driver = await createButtonDriver();
|
|
1654
|
+
* await expect(driver.component).toHaveBorderStyle("dotted", "left");
|
|
1655
|
+
* ```
|
|
1656
|
+
*/
|
|
1657
|
+
async toHaveBorderStyle(locator, expected, border = "all") {
|
|
1658
|
+
const assertionName = "toHaveBorder";
|
|
1659
|
+
let expectedBorderStyle;
|
|
1660
|
+
try {
|
|
1661
|
+
expectedBorderStyle = parseAsCSSBorderStyle(expected);
|
|
1662
|
+
if (Object.values(expectedBorderStyle).length === 0) {
|
|
1663
|
+
throw new Error("Empty border style!");
|
|
1664
|
+
}
|
|
1665
|
+
} catch (e) {
|
|
1666
|
+
return {
|
|
1667
|
+
message: () => "In " + this.utils.matcherHint(assertionName, locator, expected, { isNot: this.isNot }) + ":\n" + (e instanceof Error ? e.message : String(e)) + `
|
|
1668
|
+
|
|
1669
|
+
Expected: Correct CSS border style to expect (e.g. "1px solid black")
|
|
1670
|
+
Received: ${this.utils.printReceived(expected)}`,
|
|
1671
|
+
pass: false,
|
|
1672
|
+
name: assertionName,
|
|
1673
|
+
expected,
|
|
1674
|
+
actual: void 0
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
let _border = [];
|
|
1678
|
+
if (typeof border === "string") {
|
|
1679
|
+
if (border === "all") {
|
|
1680
|
+
_border = ["top", "bottom", "left", "right"];
|
|
1681
|
+
} else {
|
|
1682
|
+
_border = [border];
|
|
1683
|
+
}
|
|
1684
|
+
} else {
|
|
1685
|
+
_border = Array.from(new Set(border)).filter(isBorderSide);
|
|
1686
|
+
}
|
|
1687
|
+
let pass = true;
|
|
1688
|
+
let matcherResult;
|
|
1689
|
+
try {
|
|
1690
|
+
for (let b of _border) {
|
|
1691
|
+
await expect$1(locator).toHaveCSS(`border-${b}-style`, expectedBorderStyle);
|
|
1692
|
+
}
|
|
1693
|
+
} catch (e) {
|
|
1694
|
+
matcherResult = e.matcherResult;
|
|
1695
|
+
pass = false;
|
|
1696
|
+
}
|
|
1697
|
+
const message = pass ? () => this.utils.matcherHint(assertionName, void 0, void 0, { isNot: this.isNot }) + `
|
|
1698
|
+
|
|
1699
|
+
Expected: not ${this.utils.printExpected(expected)}
|
|
1700
|
+
` + (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : "") : () => this.utils.matcherHint(assertionName, void 0, void 0, { isNot: this.isNot }) + `
|
|
1701
|
+
|
|
1702
|
+
Expected: ${this.utils.printExpected(expected)}
|
|
1703
|
+
` + (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : "");
|
|
1704
|
+
return {
|
|
1705
|
+
message,
|
|
1706
|
+
pass,
|
|
1707
|
+
name: assertionName,
|
|
1708
|
+
expected,
|
|
1709
|
+
actual: matcherResult == null ? void 0 : matcherResult.actual
|
|
1710
|
+
};
|
|
1711
|
+
},
|
|
1712
|
+
/**
|
|
1713
|
+
* Asserts whether a component has the correct CSS `border` shorthand set:
|
|
1714
|
+
* `color`, `width`, `style`.
|
|
1715
|
+
*
|
|
1716
|
+
* @param expected Expected string label
|
|
1717
|
+
* @param border Border side(s), Default: **"all"**
|
|
1718
|
+
*
|
|
1719
|
+
* ---
|
|
1720
|
+
* **Usage**
|
|
1721
|
+
*
|
|
1722
|
+
* ```js
|
|
1723
|
+
* await initTestBed(`<Button label="hello" />`, {
|
|
1724
|
+
* testThemeVars: { "borderLeft-Button": "1px solid black" },
|
|
1725
|
+
* });
|
|
1726
|
+
* const driver = await createButtonDriver();
|
|
1727
|
+
* await expect(driver.component).toHaveBorder("1px solid black", "left");
|
|
1728
|
+
* ```
|
|
1729
|
+
*/
|
|
1730
|
+
async toHaveBorder(locator, expected, border = "all") {
|
|
1731
|
+
const assertionName = "toHaveBorder";
|
|
1732
|
+
let expectedBorder;
|
|
1733
|
+
try {
|
|
1734
|
+
expectedBorder = parseAsCssBorder(expected);
|
|
1735
|
+
if (Object.values(expectedBorder).length === 0) {
|
|
1736
|
+
throw new Error("Empty style!");
|
|
1737
|
+
}
|
|
1738
|
+
} catch (e) {
|
|
1739
|
+
return {
|
|
1740
|
+
message: () => "In " + this.utils.matcherHint(assertionName, locator, expected, { isNot: this.isNot }) + ":\n" + (e instanceof Error ? e.message : String(e)) + `
|
|
1741
|
+
|
|
1742
|
+
Expected: Correct CSS border style to expect (e.g. "1px solid black")
|
|
1743
|
+
Received: ${this.utils.printReceived(expected)}`,
|
|
1744
|
+
pass: false,
|
|
1745
|
+
name: assertionName,
|
|
1746
|
+
expected,
|
|
1747
|
+
actual: void 0
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
let _border = [];
|
|
1751
|
+
if (typeof border === "string") {
|
|
1752
|
+
if (border === "all") {
|
|
1753
|
+
_border = ["top", "bottom", "left", "right"];
|
|
1754
|
+
} else {
|
|
1755
|
+
_border = [border];
|
|
1756
|
+
}
|
|
1757
|
+
} else {
|
|
1758
|
+
_border = Array.from(new Set(border)).filter(isBorderSide);
|
|
1759
|
+
}
|
|
1760
|
+
let pass = true;
|
|
1761
|
+
let matcherResult;
|
|
1762
|
+
try {
|
|
1763
|
+
if (expectedBorder == null ? void 0 : expectedBorder.color) {
|
|
1764
|
+
const colorStr = `rgb(${expectedBorder.color.rgb().join(", ")})`;
|
|
1765
|
+
await expect(locator).toHaveBorderColor(colorStr, _border);
|
|
1766
|
+
}
|
|
1767
|
+
if (expectedBorder == null ? void 0 : expectedBorder.width) {
|
|
1768
|
+
await expect(locator).toHaveBorderWidth(numericCSSToString(expectedBorder.width), _border);
|
|
1769
|
+
}
|
|
1770
|
+
if (expectedBorder == null ? void 0 : expectedBorder.style) {
|
|
1771
|
+
await expect(locator).toHaveBorderStyle(expectedBorder.style, _border);
|
|
1772
|
+
}
|
|
1773
|
+
} catch (e) {
|
|
1774
|
+
matcherResult = e.matcherResult;
|
|
1775
|
+
pass = false;
|
|
1776
|
+
}
|
|
1777
|
+
const message = pass ? () => this.utils.matcherHint(assertionName, void 0, void 0, { isNot: this.isNot }) + `
|
|
1778
|
+
|
|
1779
|
+
Expected: not ${this.utils.printExpected(expected)}
|
|
1780
|
+
` + (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : "") : () => this.utils.matcherHint(assertionName, void 0, void 0, { isNot: this.isNot }) + `
|
|
1781
|
+
|
|
1782
|
+
Expected: ${this.utils.printExpected(expected)}
|
|
1783
|
+
` + (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : "");
|
|
1784
|
+
return {
|
|
1785
|
+
message,
|
|
1786
|
+
pass,
|
|
1787
|
+
name: assertionName,
|
|
1788
|
+
expected,
|
|
1789
|
+
actual: matcherResult == null ? void 0 : matcherResult.actual
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
});
|
|
1793
|
+
const isCI = ((_a = process == null ? void 0 : process.env) == null ? void 0 : _a.CI) === "true";
|
|
1794
|
+
async function getOnlyFirstLocator(page, testId) {
|
|
1795
|
+
const locators = page.getByTestId(testId);
|
|
1796
|
+
if (await locators.count() > 1) {
|
|
1797
|
+
return locators.first();
|
|
1798
|
+
}
|
|
1799
|
+
return locators;
|
|
1800
|
+
}
|
|
1801
|
+
class Clipboard {
|
|
1802
|
+
constructor(page) {
|
|
1803
|
+
this.content = "";
|
|
1804
|
+
this.page = page;
|
|
1805
|
+
}
|
|
1806
|
+
init() {
|
|
1807
|
+
return () => {
|
|
1808
|
+
window.navigator.clipboard.readText = () => Promise.resolve(this.content);
|
|
1809
|
+
window.navigator.clipboard.read = () => {
|
|
1810
|
+
throw new Error("Clipboard read not implemented in mocked environment");
|
|
1811
|
+
};
|
|
1812
|
+
window.navigator.clipboard.writeText = (text) => {
|
|
1813
|
+
this.content = text;
|
|
1814
|
+
return Promise.resolve(void 0);
|
|
1815
|
+
};
|
|
1816
|
+
window.navigator.clipboard.write = (items) => {
|
|
1817
|
+
throw new Error("Clipboard write not implemented in mocked environment");
|
|
1818
|
+
};
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
/**
|
|
1822
|
+
* Reads the text from the clipboard.
|
|
1823
|
+
*
|
|
1824
|
+
* @returns The text from the clipboard.
|
|
1825
|
+
*/
|
|
1826
|
+
async read() {
|
|
1827
|
+
const handle = await this.page.evaluateHandle(() => navigator.clipboard.readText());
|
|
1828
|
+
return handle.jsonValue();
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Writes the text to the clipboard.
|
|
1832
|
+
*
|
|
1833
|
+
* @param text - The text to write to the clipboard.
|
|
1834
|
+
*/
|
|
1835
|
+
async write(text) {
|
|
1836
|
+
await this.page.evaluate((text2) => navigator.clipboard.writeText(text2), text);
|
|
1837
|
+
}
|
|
1838
|
+
async clear() {
|
|
1839
|
+
await this.page.evaluate(() => navigator.clipboard.writeText(""));
|
|
1840
|
+
}
|
|
1841
|
+
/**
|
|
1842
|
+
* Copies the text from the given locator to the clipboard.
|
|
1843
|
+
*
|
|
1844
|
+
* Steps:
|
|
1845
|
+
* 1. Focus the locator.
|
|
1846
|
+
* 2. Select the text.
|
|
1847
|
+
* 3. Copy the text to the clipboard. (ControlOrMeta+C)
|
|
1848
|
+
*
|
|
1849
|
+
* @param locator - a Locator to focus and copy from
|
|
1850
|
+
*/
|
|
1851
|
+
async copy(locator) {
|
|
1852
|
+
await locator.focus();
|
|
1853
|
+
await locator.selectText();
|
|
1854
|
+
await this.page.keyboard.press("ControlOrMeta+C");
|
|
1855
|
+
}
|
|
1856
|
+
/**
|
|
1857
|
+
* Pastes the text from the clipboard to the given locator.
|
|
1858
|
+
*
|
|
1859
|
+
* Steps:
|
|
1860
|
+
* 1. Focus the locator.
|
|
1861
|
+
* 2. Paste the text from the clipboard. (ControlOrMeta+V)
|
|
1862
|
+
*
|
|
1863
|
+
* @param locator - a Locator to focus and paste to
|
|
1864
|
+
*/
|
|
1865
|
+
async paste(locator) {
|
|
1866
|
+
await locator.focus();
|
|
1867
|
+
await this.page.keyboard.press("ControlOrMeta+V");
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
function mapThemeRelatedVars(description) {
|
|
1871
|
+
const { themes, defaultTheme, testThemeVars, ...rest } = description ?? {};
|
|
1872
|
+
if (themes) {
|
|
1873
|
+
return { themes, defaultTheme: defaultTheme ?? "xmlui", ...rest };
|
|
1874
|
+
}
|
|
1875
|
+
const testTheme = {
|
|
1876
|
+
id: "test",
|
|
1877
|
+
name: "Test",
|
|
1878
|
+
extends: "xmlui",
|
|
1879
|
+
themeVars: testThemeVars
|
|
1880
|
+
};
|
|
1881
|
+
return { themes: [testTheme], defaultTheme: "test", ...rest };
|
|
1882
|
+
}
|
|
1883
|
+
const test = test$1.extend({
|
|
1884
|
+
// NOTE: the base Playwright test can be extended with fixture methods
|
|
1885
|
+
// as well as any other language constructs we deem useful
|
|
1886
|
+
baseComponentTestId: "test-id-component",
|
|
1887
|
+
testStateViewTestId: "test-state-view-testid",
|
|
1888
|
+
initTestBed: async ({ page, baseComponentTestId, testStateViewTestId }, use) => {
|
|
1889
|
+
await use(async (source, description) => {
|
|
1890
|
+
var _a2, _b, _c;
|
|
1891
|
+
const defaultTestResources = {
|
|
1892
|
+
"icon.box": "/resources/box.svg",
|
|
1893
|
+
"icon.doc": "/resources/doc.svg",
|
|
1894
|
+
"icon.sun": "/resources/sun.svg",
|
|
1895
|
+
"icon.eye": "/resources/eye.svg",
|
|
1896
|
+
"icon.txt": "/resources/txt.svg",
|
|
1897
|
+
"icon.bell": "/resources/bell.svg"
|
|
1898
|
+
};
|
|
1899
|
+
const { errors, component } = xmlUiMarkupToComponent(`
|
|
1900
|
+
<Fragment var.testState="{null}">
|
|
1901
|
+
${source}
|
|
1902
|
+
<Stack width="0" height="0">
|
|
1903
|
+
<Text
|
|
1904
|
+
testId="${testStateViewTestId}"
|
|
1905
|
+
value="{ typeof testState === 'undefined' ? 'undefined' : JSON.stringify(testState) }"/>
|
|
1906
|
+
</Stack>
|
|
1907
|
+
</Fragment>
|
|
1908
|
+
`);
|
|
1909
|
+
if (errors.length > 0) {
|
|
1910
|
+
throw { errors };
|
|
1911
|
+
}
|
|
1912
|
+
const entryPoint = component;
|
|
1913
|
+
const components = (_a2 = description == null ? void 0 : description.components) == null ? void 0 : _a2.map((c) => {
|
|
1914
|
+
const { component: component2, errors: errors2, erroneousCompoundComponentName } = parseComponentIfNecessary(c);
|
|
1915
|
+
if (erroneousCompoundComponentName) {
|
|
1916
|
+
throw new Error(
|
|
1917
|
+
`Error parsing component "${erroneousCompoundComponentName}": ${errors2.join("\n")}`
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
return component2;
|
|
1921
|
+
});
|
|
1922
|
+
if (source !== "" && entryPoint.children) {
|
|
1923
|
+
const sourceBaseComponent = entryPoint.children[0];
|
|
1924
|
+
const isCompoundComponentRoot = components && (components == null ? void 0 : components.length) > 0 && (components == null ? void 0 : components.map((c) => c.name).includes(sourceBaseComponent.type));
|
|
1925
|
+
if (!isCompoundComponentRoot && !sourceBaseComponent.testId) {
|
|
1926
|
+
sourceBaseComponent.testId = baseComponentTestId;
|
|
1927
|
+
} else if (isCompoundComponentRoot && ((_b = sourceBaseComponent.children) == null ? void 0 : _b.length) === 1 && !((_c = sourceBaseComponent.children) == null ? void 0 : _c[0].testId)) {
|
|
1928
|
+
sourceBaseComponent.children[0].testId = baseComponentTestId;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
const themedDescription = mapThemeRelatedVars(description);
|
|
1932
|
+
const mergedResources = {
|
|
1933
|
+
...defaultTestResources,
|
|
1934
|
+
...themedDescription.resources
|
|
1935
|
+
};
|
|
1936
|
+
const _appDescription = {
|
|
1937
|
+
name: "test bed app",
|
|
1938
|
+
...themedDescription,
|
|
1939
|
+
resources: mergedResources,
|
|
1940
|
+
components,
|
|
1941
|
+
entryPoint
|
|
1942
|
+
};
|
|
1943
|
+
const clipboard = new Clipboard(page);
|
|
1944
|
+
if (isCI) {
|
|
1945
|
+
await page.addInitScript(clipboard.init);
|
|
1946
|
+
}
|
|
1947
|
+
await page.addInitScript((app) => {
|
|
1948
|
+
window.TEST_ENV = app;
|
|
1949
|
+
}, _appDescription);
|
|
1950
|
+
const { width, height } = page.viewportSize();
|
|
1951
|
+
await page.goto("/");
|
|
1952
|
+
const testIcons = {
|
|
1953
|
+
boxIcon: page.getByTestId("box-svg"),
|
|
1954
|
+
docIcon: page.getByTestId("doc-svg"),
|
|
1955
|
+
sunIcon: page.getByTestId("sun-svg"),
|
|
1956
|
+
eyeIcon: page.getByTestId("eye-svg"),
|
|
1957
|
+
txtIcon: page.getByTestId("txt-svg"),
|
|
1958
|
+
bellIcon: page.getByTestId("bell-svg")
|
|
1959
|
+
};
|
|
1960
|
+
return {
|
|
1961
|
+
testStateDriver: new TestStateDriver(page.getByTestId(testStateViewTestId)),
|
|
1962
|
+
clipboard,
|
|
1963
|
+
width: width ?? 0,
|
|
1964
|
+
height: height ?? 0,
|
|
1965
|
+
testIcons
|
|
1966
|
+
};
|
|
1967
|
+
});
|
|
1968
|
+
},
|
|
1969
|
+
createDriver: async ({ page, baseComponentTestId }, use) => {
|
|
1970
|
+
await use(async (driverClass, testIdOrLocator) => {
|
|
1971
|
+
let locator = void 0;
|
|
1972
|
+
if (testIdOrLocator === void 0 || typeof testIdOrLocator === "string") {
|
|
1973
|
+
locator = await getOnlyFirstLocator(page, testIdOrLocator ?? baseComponentTestId);
|
|
1974
|
+
} else if (typeof testIdOrLocator === "object") {
|
|
1975
|
+
locator = testIdOrLocator;
|
|
1976
|
+
}
|
|
1977
|
+
return new driverClass({
|
|
1978
|
+
locator,
|
|
1979
|
+
page
|
|
1980
|
+
});
|
|
1981
|
+
});
|
|
1982
|
+
},
|
|
1983
|
+
createButtonDriver: async ({ createDriver }, use) => {
|
|
1984
|
+
await use((testIdOrLocator) => {
|
|
1985
|
+
return createDriver(ButtonDriver, testIdOrLocator);
|
|
1986
|
+
});
|
|
1987
|
+
},
|
|
1988
|
+
createBackdropDriver: async ({ createDriver }, use) => {
|
|
1989
|
+
await use((testIdOrLocator) => {
|
|
1990
|
+
return createDriver(BackdropDriver, testIdOrLocator);
|
|
1991
|
+
});
|
|
1992
|
+
},
|
|
1993
|
+
createContentSeparatorDriver: async ({ createDriver }, use) => {
|
|
1994
|
+
await use((testIdOrLocator) => {
|
|
1995
|
+
return createDriver(ContentSeparatorDriver, testIdOrLocator);
|
|
1996
|
+
});
|
|
1997
|
+
},
|
|
1998
|
+
createAvatarDriver: async ({ createDriver }, use) => {
|
|
1999
|
+
await use((testIdOrLocator) => {
|
|
2000
|
+
return createDriver(AvatarDriver, testIdOrLocator);
|
|
2001
|
+
});
|
|
2002
|
+
},
|
|
2003
|
+
createFormDriver: async ({ createDriver }, use) => {
|
|
2004
|
+
await use((testIdOrLocator) => {
|
|
2005
|
+
return createDriver(FormDriver, testIdOrLocator);
|
|
2006
|
+
});
|
|
2007
|
+
},
|
|
2008
|
+
createFormItemDriver: async ({ createDriver }, use) => {
|
|
2009
|
+
await use((testIdOrLocator) => {
|
|
2010
|
+
return createDriver(FormItemDriver, testIdOrLocator);
|
|
2011
|
+
});
|
|
2012
|
+
},
|
|
2013
|
+
createValidationSummaryDriver: async ({ createDriver }, use) => {
|
|
2014
|
+
await use((testIdOrLocator) => {
|
|
2015
|
+
return createDriver(ValidationSummaryDriver, testIdOrLocator);
|
|
2016
|
+
});
|
|
2017
|
+
},
|
|
2018
|
+
createValidationDisplayDriver: async ({ createDriver }, use) => {
|
|
2019
|
+
await use((testIdOrLocator) => {
|
|
2020
|
+
return createDriver(ValidationDisplayDriver, testIdOrLocator);
|
|
2021
|
+
});
|
|
2022
|
+
},
|
|
2023
|
+
createSplitterDriver: async ({ createDriver }, use) => {
|
|
2024
|
+
await use((testIdOrLocator) => {
|
|
2025
|
+
return createDriver(SplitterDriver, testIdOrLocator);
|
|
2026
|
+
});
|
|
2027
|
+
},
|
|
2028
|
+
createMarkdownDriver: async ({ createDriver }, use) => {
|
|
2029
|
+
await use((testIdOrLocator) => {
|
|
2030
|
+
return createDriver(MarkdownDriver, testIdOrLocator);
|
|
2031
|
+
});
|
|
2032
|
+
},
|
|
2033
|
+
createItemsDriver: async ({ createDriver }, use) => {
|
|
2034
|
+
await use((testIdOrLocator) => {
|
|
2035
|
+
return createDriver(ItemsDriver, testIdOrLocator);
|
|
2036
|
+
});
|
|
2037
|
+
},
|
|
2038
|
+
createRangeDriver: async ({ createDriver }, use) => {
|
|
2039
|
+
await use((testIdOrLocator) => {
|
|
2040
|
+
return createDriver(RangeDriver, testIdOrLocator);
|
|
2041
|
+
});
|
|
2042
|
+
},
|
|
2043
|
+
createDatePickerDriver: async ({ createDriver }, use) => {
|
|
2044
|
+
await use((testIdOrLocator) => {
|
|
2045
|
+
return createDriver(DatePickerDriver, testIdOrLocator);
|
|
2046
|
+
});
|
|
2047
|
+
},
|
|
2048
|
+
createExpandableItemDriver: async ({ createDriver }, use) => {
|
|
2049
|
+
await use((testIdOrLocator) => {
|
|
2050
|
+
return createDriver(ExpandableItemDriver, testIdOrLocator);
|
|
2051
|
+
});
|
|
2052
|
+
},
|
|
2053
|
+
createFileInputDriver: async ({ createDriver }, use) => {
|
|
2054
|
+
await use((testIdOrLocator) => {
|
|
2055
|
+
return createDriver(FileInputDriver, testIdOrLocator);
|
|
2056
|
+
});
|
|
2057
|
+
},
|
|
2058
|
+
createFileUploadDropZoneDriver: async ({ createDriver }, use) => {
|
|
2059
|
+
await use((testIdOrLocator) => {
|
|
2060
|
+
return createDriver(FileUploadDropZoneDriver, testIdOrLocator);
|
|
2061
|
+
});
|
|
2062
|
+
},
|
|
2063
|
+
createAutoCompleteDriver: async ({ createDriver }, use) => {
|
|
2064
|
+
await use((testIdOrLocator) => {
|
|
2065
|
+
return createDriver(AutoCompleteDriver, testIdOrLocator);
|
|
2066
|
+
});
|
|
2067
|
+
},
|
|
2068
|
+
createSelectDriver: async ({ createDriver }, use) => {
|
|
2069
|
+
await use((testIdOrLocator) => {
|
|
2070
|
+
return createDriver(SelectDriver, testIdOrLocator);
|
|
2071
|
+
});
|
|
2072
|
+
},
|
|
2073
|
+
createRadioGroupDriver: async ({ createDriver }, use) => {
|
|
2074
|
+
await use((testIdOrLocator) => {
|
|
2075
|
+
return createDriver(RadioGroupDriver, testIdOrLocator);
|
|
2076
|
+
});
|
|
2077
|
+
},
|
|
2078
|
+
createNumberBoxDriver: async ({ createDriver }, use) => {
|
|
2079
|
+
await use((testIdOrLocator) => {
|
|
2080
|
+
return createDriver(NumberBoxDriver, testIdOrLocator);
|
|
2081
|
+
});
|
|
2082
|
+
},
|
|
2083
|
+
createTextBoxDriver: async ({ createDriver }, use) => {
|
|
2084
|
+
await use((testIdOrLocator) => {
|
|
2085
|
+
return createDriver(TextBoxDriver, testIdOrLocator);
|
|
2086
|
+
});
|
|
2087
|
+
},
|
|
2088
|
+
createSliderDriver: async ({ createDriver }, use) => {
|
|
2089
|
+
await use((testIdOrLocator) => {
|
|
2090
|
+
return createDriver(SliderDriver, testIdOrLocator);
|
|
2091
|
+
});
|
|
2092
|
+
},
|
|
2093
|
+
createTextAreaDriver: async ({ createDriver }, use) => {
|
|
2094
|
+
await use((testIdOrLocator) => {
|
|
2095
|
+
return createDriver(TextAreaDriver, testIdOrLocator);
|
|
2096
|
+
});
|
|
2097
|
+
},
|
|
2098
|
+
createProgressBarDriver: async ({ createDriver }, use) => {
|
|
2099
|
+
await use((testIdOrLocator) => {
|
|
2100
|
+
return createDriver(ProgressBarDriver, testIdOrLocator);
|
|
2101
|
+
});
|
|
2102
|
+
},
|
|
2103
|
+
createListDriver: async ({ createDriver }, use) => {
|
|
2104
|
+
await use((testIdOrLocator) => {
|
|
2105
|
+
return createDriver(ListDriver, testIdOrLocator);
|
|
2106
|
+
});
|
|
2107
|
+
},
|
|
2108
|
+
createTextDriver: async ({ createDriver }, use) => {
|
|
2109
|
+
await use((testIdOrLocator) => {
|
|
2110
|
+
return createDriver(TextDriver, testIdOrLocator);
|
|
2111
|
+
});
|
|
2112
|
+
},
|
|
2113
|
+
createHeadingDriver: async ({ createDriver }, use) => {
|
|
2114
|
+
await use((testIdOrLocator) => {
|
|
2115
|
+
return createDriver(HeadingDriver, testIdOrLocator);
|
|
2116
|
+
});
|
|
2117
|
+
},
|
|
2118
|
+
createIconDriver: async ({ createDriver }, use) => {
|
|
2119
|
+
await use((testIdOrLocator) => {
|
|
2120
|
+
return createDriver(IconDriver, testIdOrLocator);
|
|
2121
|
+
});
|
|
2122
|
+
},
|
|
2123
|
+
createStackDriver: async ({ createDriver }, use) => {
|
|
2124
|
+
await use((testIdOrLocator) => {
|
|
2125
|
+
return createDriver(StackDriver, testIdOrLocator);
|
|
2126
|
+
});
|
|
2127
|
+
},
|
|
2128
|
+
createHStackDriver: async ({ createDriver }, use) => {
|
|
2129
|
+
await use((testIdOrLocator) => {
|
|
2130
|
+
return createDriver(HStackDriver, testIdOrLocator);
|
|
2131
|
+
});
|
|
2132
|
+
},
|
|
2133
|
+
createVStackDriver: async ({ createDriver }, use) => {
|
|
2134
|
+
await use((testIdOrLocator) => {
|
|
2135
|
+
return createDriver(VStackDriver, testIdOrLocator);
|
|
2136
|
+
});
|
|
2137
|
+
},
|
|
2138
|
+
createLinkDriver: async ({ createDriver }, use) => {
|
|
2139
|
+
await use((testIdOrLocator) => {
|
|
2140
|
+
return createDriver(LinkDriver, testIdOrLocator);
|
|
2141
|
+
});
|
|
2142
|
+
},
|
|
2143
|
+
createNavLinkDriver: async ({ createDriver }, use) => {
|
|
2144
|
+
await use((testIdOrLocator) => {
|
|
2145
|
+
return createDriver(NavLinkDriver, testIdOrLocator);
|
|
2146
|
+
});
|
|
2147
|
+
},
|
|
2148
|
+
createNavGroupDriver: async ({ createDriver }, use) => {
|
|
2149
|
+
await use((testIdOrLocator) => {
|
|
2150
|
+
return createDriver(NavGroupDriver, testIdOrLocator);
|
|
2151
|
+
});
|
|
2152
|
+
},
|
|
2153
|
+
createNavPanelDriver: async ({ createDriver }, use) => {
|
|
2154
|
+
await use((testIdOrLocator) => {
|
|
2155
|
+
return createDriver(NavPanelDriver, testIdOrLocator);
|
|
2156
|
+
});
|
|
2157
|
+
},
|
|
2158
|
+
createCardDriver: async ({ createDriver }, use) => {
|
|
2159
|
+
await use((testIdOrLocator) => {
|
|
2160
|
+
return createDriver(CardDriver, testIdOrLocator);
|
|
2161
|
+
});
|
|
2162
|
+
},
|
|
2163
|
+
createAccordionDriver: async ({ createDriver }, use) => {
|
|
2164
|
+
await use((testIdOrLocator) => {
|
|
2165
|
+
return createDriver(AccordionDriver, testIdOrLocator);
|
|
2166
|
+
});
|
|
2167
|
+
},
|
|
2168
|
+
createAppHeaderDriver: async ({ createDriver }, use) => {
|
|
2169
|
+
await use((testIdOrLocator) => {
|
|
2170
|
+
return createDriver(AppHeaderDriver, testIdOrLocator);
|
|
2171
|
+
});
|
|
2172
|
+
},
|
|
2173
|
+
createAppFooterDriver: async ({ createDriver }, use) => {
|
|
2174
|
+
await use((testIdOrLocator) => {
|
|
2175
|
+
return createDriver(AppFooterDriver, testIdOrLocator);
|
|
2176
|
+
});
|
|
2177
|
+
},
|
|
2178
|
+
createBadgeDriver: async ({ createDriver }, use) => {
|
|
2179
|
+
await use((testIdOrLocator) => {
|
|
2180
|
+
return createDriver(BadgeDriver, testIdOrLocator);
|
|
2181
|
+
});
|
|
2182
|
+
},
|
|
2183
|
+
createNoResultDriver: async ({ createDriver }, use) => {
|
|
2184
|
+
await use((testIdOrLocator) => {
|
|
2185
|
+
return createDriver(NoResultDriver, testIdOrLocator);
|
|
2186
|
+
});
|
|
2187
|
+
},
|
|
2188
|
+
createOptionDriver: async ({ createDriver }, use) => {
|
|
2189
|
+
await use((testIdOrLocator) => {
|
|
2190
|
+
return createDriver(OptionDriver, testIdOrLocator);
|
|
2191
|
+
});
|
|
2192
|
+
},
|
|
2193
|
+
createHtmlTagDriver: async ({ createDriver }, use) => {
|
|
2194
|
+
await use((testIdOrLocator) => {
|
|
2195
|
+
return createDriver(HtmlTagDriver, testIdOrLocator);
|
|
2196
|
+
});
|
|
2197
|
+
},
|
|
2198
|
+
createCodeBlockDriver: async ({ createDriver }, use) => {
|
|
2199
|
+
await use((testIdOrLocator) => {
|
|
2200
|
+
return createDriver(CodeBlockDriver, testIdOrLocator);
|
|
2201
|
+
});
|
|
2202
|
+
},
|
|
2203
|
+
createCheckboxDriver: async ({ createDriver }, use) => {
|
|
2204
|
+
await use((testIdOrLocator) => {
|
|
2205
|
+
return createDriver(CheckboxDriver, testIdOrLocator);
|
|
2206
|
+
});
|
|
2207
|
+
},
|
|
2208
|
+
createLabelDriver: async ({ createDriver }, use) => {
|
|
2209
|
+
await use((testIdOrLocator) => {
|
|
2210
|
+
return createDriver(LabelDriver, testIdOrLocator);
|
|
2211
|
+
});
|
|
2212
|
+
},
|
|
2213
|
+
createSpinnerDriver: async ({ createDriver }, use) => {
|
|
2214
|
+
await use((testIdOrLocator) => {
|
|
2215
|
+
return createDriver(SpinnerDriver, testIdOrLocator);
|
|
2216
|
+
});
|
|
2217
|
+
},
|
|
2218
|
+
createDropdownMenuDriver: async ({ createDriver }, use) => {
|
|
2219
|
+
await use((testIdOrLocator) => {
|
|
2220
|
+
return createDriver(DropdownMenuDriver, testIdOrLocator);
|
|
2221
|
+
});
|
|
2222
|
+
},
|
|
2223
|
+
createTimeInputDriver: async ({ createDriver }, use) => {
|
|
2224
|
+
await use((testIdOrLocator) => {
|
|
2225
|
+
return createDriver(TimeInputDriver, testIdOrLocator);
|
|
2226
|
+
});
|
|
2227
|
+
},
|
|
2228
|
+
createTimerDriver: async ({ createDriver }, use) => {
|
|
2229
|
+
await use((testIdOrLocator) => {
|
|
2230
|
+
return createDriver(TimerDriver, testIdOrLocator);
|
|
2231
|
+
});
|
|
2232
|
+
},
|
|
2233
|
+
createDateInputDriver: async ({ createDriver }, use) => {
|
|
2234
|
+
await use((testIdOrLocator) => {
|
|
2235
|
+
return createDriver(DateInputDriver, testIdOrLocator);
|
|
2236
|
+
});
|
|
2237
|
+
},
|
|
2238
|
+
createModalDialogDriver: async ({ createDriver }, use) => {
|
|
2239
|
+
await use((testIdOrLocator) => {
|
|
2240
|
+
return createDriver(ModalDialogDriver, testIdOrLocator);
|
|
2241
|
+
});
|
|
2242
|
+
},
|
|
2243
|
+
createTreeDriver: async ({ createDriver }, use) => {
|
|
2244
|
+
await use((testIdOrLocator) => {
|
|
2245
|
+
return createDriver(TreeDriver, testIdOrLocator);
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
});
|
|
2249
|
+
function parseComponent(entryPoint) {
|
|
2250
|
+
if (typeof entryPoint === "string") {
|
|
2251
|
+
return xmlUiMarkupToComponent(entryPoint).component;
|
|
2252
|
+
}
|
|
2253
|
+
return entryPoint;
|
|
2254
|
+
}
|
|
2255
|
+
async function initApp(page, appDescription, url = "/", resources = {}) {
|
|
2256
|
+
const { entryPoint, components } = appDescription;
|
|
2257
|
+
const _appDescription = {
|
|
2258
|
+
...appDescription,
|
|
2259
|
+
name: appDescription.name || "Test App",
|
|
2260
|
+
entryPoint: parseComponent(entryPoint),
|
|
2261
|
+
resources,
|
|
2262
|
+
components: !components ? void 0 : Array.isArray(components) ? components.map((comp) => parseComponent(comp)) : [parseComponent(components)]
|
|
2263
|
+
};
|
|
2264
|
+
await page.addInitScript((app) => {
|
|
2265
|
+
window.TEST_ENV = app;
|
|
2266
|
+
}, _appDescription);
|
|
2267
|
+
await page.goto(url);
|
|
2268
|
+
}
|
|
2269
|
+
async function prepPage(co, appDesc, url = "/") {
|
|
2270
|
+
const context = co;
|
|
2271
|
+
const page = await context.newPage();
|
|
2272
|
+
await initApp(page, appDesc, url);
|
|
2273
|
+
return page;
|
|
2274
|
+
}
|
|
2275
|
+
async function initThemedApp(page, entryPoint, theme) {
|
|
2276
|
+
theme.id ?? (theme.id = "testTheme");
|
|
2277
|
+
theme.name ?? (theme.name = "Custom Test theme");
|
|
2278
|
+
await initApp(page, { entryPoint, defaultTheme: theme.id, themes: [theme] });
|
|
2279
|
+
}
|
|
2280
|
+
function scalePercentBy(scalarOf100Percent, percentage) {
|
|
2281
|
+
if (!percentage.endsWith("%")) {
|
|
2282
|
+
throw new Error("argument doesn't end with % sign");
|
|
2283
|
+
}
|
|
2284
|
+
const percentageNum = Number(percentage.slice(0, -1));
|
|
2285
|
+
return scalarOf100Percent / 100 * percentageNum;
|
|
2286
|
+
}
|
|
2287
|
+
function getBoundingRect(locator) {
|
|
2288
|
+
return locator.evaluate((element) => element.getBoundingClientRect());
|
|
2289
|
+
}
|
|
2290
|
+
async function getFullRectangle(locator) {
|
|
2291
|
+
const boundingRect = await locator.evaluate((element) => element.getBoundingClientRect());
|
|
2292
|
+
const margins = await getElementStyles(locator, [
|
|
2293
|
+
"margin-left",
|
|
2294
|
+
"margin-right",
|
|
2295
|
+
"margin-top",
|
|
2296
|
+
"margin-bottom"
|
|
2297
|
+
]);
|
|
2298
|
+
const marginLeft = parseFloat(margins["margin-left"]);
|
|
2299
|
+
const marginRight = parseFloat(margins["margin-right"]);
|
|
2300
|
+
const marginTop = parseFloat(margins["margin-top"]);
|
|
2301
|
+
const marginBottom = parseFloat(margins["margin-bottom"]);
|
|
2302
|
+
const width = boundingRect.width + marginLeft + marginRight;
|
|
2303
|
+
const height = boundingRect.height + marginTop + marginBottom;
|
|
2304
|
+
const left = boundingRect.left - marginLeft;
|
|
2305
|
+
const right = boundingRect.right + marginRight;
|
|
2306
|
+
const top = boundingRect.top - marginTop;
|
|
2307
|
+
const bottom = boundingRect.bottom + marginBottom;
|
|
2308
|
+
return { width, height, left, right, top, bottom };
|
|
2309
|
+
}
|
|
2310
|
+
async function isElementOverflown(locator, direction = "both") {
|
|
2311
|
+
const [width, height, scrollWidth, scrollHeight] = await locator.evaluate((element) => [
|
|
2312
|
+
element.clientWidth,
|
|
2313
|
+
element.clientHeight,
|
|
2314
|
+
element.scrollWidth,
|
|
2315
|
+
element.scrollHeight
|
|
2316
|
+
]);
|
|
2317
|
+
if (direction === "x") return scrollWidth > width;
|
|
2318
|
+
if (direction === "y") return scrollHeight > height;
|
|
2319
|
+
return scrollWidth > width && scrollHeight > height;
|
|
2320
|
+
}
|
|
2321
|
+
function pixelStrToNum(pixelStr) {
|
|
2322
|
+
return Number(pixelStr.replace("px", ""));
|
|
2323
|
+
}
|
|
2324
|
+
function getElementStyle(locator, style) {
|
|
2325
|
+
return locator.evaluate(
|
|
2326
|
+
(element, style2) => window.getComputedStyle(element).getPropertyValue(style2),
|
|
2327
|
+
style
|
|
2328
|
+
);
|
|
2329
|
+
}
|
|
2330
|
+
function getElementStyles(locator, styles = []) {
|
|
2331
|
+
return locator.evaluate(
|
|
2332
|
+
(element, styles2) => Object.fromEntries(
|
|
2333
|
+
styles2.map((styleName) => [
|
|
2334
|
+
styleName,
|
|
2335
|
+
window.getComputedStyle(element).getPropertyValue(styleName)
|
|
2336
|
+
])
|
|
2337
|
+
),
|
|
2338
|
+
styles
|
|
2339
|
+
);
|
|
2340
|
+
}
|
|
2341
|
+
async function getStyle(page, testId, style) {
|
|
2342
|
+
const locator = page.getByTestId(testId);
|
|
2343
|
+
return await getElementStyle(locator, style);
|
|
2344
|
+
}
|
|
2345
|
+
export {
|
|
2346
|
+
ComponentDriver,
|
|
2347
|
+
DateInputDriver,
|
|
2348
|
+
SKIP_REASON,
|
|
2349
|
+
TimeInputDriver,
|
|
2350
|
+
TimerDriver,
|
|
2351
|
+
expect,
|
|
2352
|
+
getBorders,
|
|
2353
|
+
getBoundingRect$1 as getBoundingRect,
|
|
2354
|
+
getBounds,
|
|
2355
|
+
getComponentTagName,
|
|
2356
|
+
getElementStyle$1 as getElementStyle,
|
|
2357
|
+
getElementStyles$1 as getElementStyles,
|
|
2358
|
+
getFullRectangle,
|
|
2359
|
+
getHtmlAttributes,
|
|
2360
|
+
getMargins,
|
|
2361
|
+
getPaddings,
|
|
2362
|
+
getPseudoStyles,
|
|
2363
|
+
getStyles,
|
|
2364
|
+
getBoundingRect as getThemedBoundingRect,
|
|
2365
|
+
getElementStyle as getThemedElementStyle,
|
|
2366
|
+
getElementStyles as getThemedElementStyles,
|
|
2367
|
+
getStyle as getThemedStyle,
|
|
2368
|
+
initApp,
|
|
2369
|
+
initThemedApp,
|
|
2370
|
+
isIndeterminate,
|
|
2371
|
+
mapObject,
|
|
2372
|
+
numericCSSToString,
|
|
2373
|
+
overflows,
|
|
2374
|
+
parseAsCSSBorderStyle,
|
|
2375
|
+
parseAsCSSColor,
|
|
2376
|
+
parseAsCssBorder,
|
|
2377
|
+
parseAsNumericCss,
|
|
2378
|
+
parseComponentIfNecessary,
|
|
2379
|
+
pixelStrToNum$1 as pixelStrToNum,
|
|
2380
|
+
prepPage,
|
|
2381
|
+
scaleByPercent,
|
|
2382
|
+
scalePercentBy,
|
|
2383
|
+
test,
|
|
2384
|
+
isElementOverflown as themedOverflows,
|
|
2385
|
+
pixelStrToNum as themedPixelStrToNum
|
|
2386
|
+
};
|