@letsrunit/playwright 0.21.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +189 -174
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/fallback-locator.ts +6 -0
- package/src/field/index.ts +1 -3
- package/src/utils/pick-field-element.ts +18 -3
package/dist/index.js
CHANGED
|
@@ -27,10 +27,196 @@ async function browse(browser, options = {}) {
|
|
|
27
27
|
return await context.newPage();
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// src/fallback-locator.ts
|
|
31
|
+
var FALLBACK_LOCATOR_CANDIDATES = /* @__PURE__ */ Symbol("letsrunit.playwright.fallback-locator-candidates");
|
|
32
|
+
var ACTION_METHODS = /* @__PURE__ */ new Set([
|
|
33
|
+
"blur",
|
|
34
|
+
"check",
|
|
35
|
+
"clear",
|
|
36
|
+
"click",
|
|
37
|
+
"dblclick",
|
|
38
|
+
"dispatchEvent",
|
|
39
|
+
"dragTo",
|
|
40
|
+
"fill",
|
|
41
|
+
"focus",
|
|
42
|
+
"hover",
|
|
43
|
+
"press",
|
|
44
|
+
"pressSequentially",
|
|
45
|
+
"scrollIntoViewIfNeeded",
|
|
46
|
+
"selectOption",
|
|
47
|
+
"setChecked",
|
|
48
|
+
"setInputFiles",
|
|
49
|
+
"tap",
|
|
50
|
+
"type",
|
|
51
|
+
"uncheck"
|
|
52
|
+
]);
|
|
53
|
+
var LOCATOR_CHAIN_METHODS = /* @__PURE__ */ new Set([
|
|
54
|
+
"and",
|
|
55
|
+
"first",
|
|
56
|
+
"last",
|
|
57
|
+
"locator",
|
|
58
|
+
"nth",
|
|
59
|
+
"or"
|
|
60
|
+
]);
|
|
61
|
+
var NO_WAIT_OPTION_INDEX = {
|
|
62
|
+
dragTo: 1,
|
|
63
|
+
fill: 1,
|
|
64
|
+
press: 1,
|
|
65
|
+
pressSequentially: 1,
|
|
66
|
+
selectOption: 1,
|
|
67
|
+
setInputFiles: 1,
|
|
68
|
+
type: 1
|
|
69
|
+
};
|
|
70
|
+
function buildOrLocator(candidates) {
|
|
71
|
+
let result = candidates[0];
|
|
72
|
+
for (const candidate of candidates.slice(1)) {
|
|
73
|
+
result = result.or(candidate);
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
async function firstPresentFallback(candidates) {
|
|
78
|
+
for (const candidate of candidates.slice(1)) {
|
|
79
|
+
try {
|
|
80
|
+
if (await candidate.count() > 0) return candidate;
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function withNoWaitTimeout(method, args) {
|
|
87
|
+
const next = [...args];
|
|
88
|
+
const optionIndex = NO_WAIT_OPTION_INDEX[method] ?? 0;
|
|
89
|
+
const current = next[optionIndex];
|
|
90
|
+
if (current && typeof current === "object") {
|
|
91
|
+
next[optionIndex] = { ...current, timeout: 0 };
|
|
92
|
+
} else {
|
|
93
|
+
next[optionIndex] = { timeout: 0 };
|
|
94
|
+
}
|
|
95
|
+
return next;
|
|
96
|
+
}
|
|
97
|
+
function handleExpect(primary, candidates) {
|
|
98
|
+
return (expression, options) => {
|
|
99
|
+
if (expression.includes("to.be.visible") || expression.includes("to.be.attached")) {
|
|
100
|
+
return buildOrLocator(candidates)._expect(expression, options);
|
|
101
|
+
}
|
|
102
|
+
return primary._expect(expression, options);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function handleAll(candidates) {
|
|
106
|
+
return async () => {
|
|
107
|
+
const all = [];
|
|
108
|
+
for (const candidate of candidates) {
|
|
109
|
+
all.push(...await candidate.all());
|
|
110
|
+
}
|
|
111
|
+
return all;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function handleLocatorChain(prop, candidates) {
|
|
115
|
+
return (...args) => createFallbackLocator(
|
|
116
|
+
candidates.map((candidate) => {
|
|
117
|
+
const method = candidate[prop];
|
|
118
|
+
return method.apply(candidate, args);
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
function handleCount(primary, candidates) {
|
|
123
|
+
return async () => {
|
|
124
|
+
const primaryCount = await primary.count();
|
|
125
|
+
if (primaryCount > 0) return primaryCount;
|
|
126
|
+
const fallback = await firstPresentFallback(candidates);
|
|
127
|
+
if (!fallback) return primaryCount;
|
|
128
|
+
return fallback.count();
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function handleAction(prop, primary, candidates, primaryMethod) {
|
|
132
|
+
return async (...args) => {
|
|
133
|
+
try {
|
|
134
|
+
return await primaryMethod.apply(primary, args);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
const fallback = await firstPresentFallback(candidates);
|
|
137
|
+
if (!fallback) throw error;
|
|
138
|
+
const fallbackMethod = fallback[prop];
|
|
139
|
+
return fallbackMethod.apply(fallback, withNoWaitTimeout(prop, args));
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function handleAsyncFallback(prop, primaryMethod, primary, candidates) {
|
|
144
|
+
return (...args) => {
|
|
145
|
+
const result = primaryMethod.apply(primary, args);
|
|
146
|
+
if (!result || typeof result !== "object" || typeof result.catch !== "function") {
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
return result.catch(async (error) => {
|
|
150
|
+
const fallback = await firstPresentFallback(candidates);
|
|
151
|
+
if (!fallback) throw error;
|
|
152
|
+
const fallbackMethod = fallback[prop];
|
|
153
|
+
return fallbackMethod.apply(fallback, args);
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function formatLocatorChain(candidates) {
|
|
158
|
+
const parts = candidates.map((candidate) => candidate.toString());
|
|
159
|
+
if (parts.length === 0) return "";
|
|
160
|
+
if (parts.length === 1) return parts[0];
|
|
161
|
+
return `${parts[0]} {fuzzy}`;
|
|
162
|
+
}
|
|
163
|
+
function createFallbackLocator(candidates) {
|
|
164
|
+
const primary = candidates[0];
|
|
165
|
+
const unsupported = /* @__PURE__ */ new Set(["filter", "getByRole", "getByLabel"]);
|
|
166
|
+
const passthroughMetaProperties = /* @__PURE__ */ new Set(["constructor", "__proto__"]);
|
|
167
|
+
const proxy = new Proxy(primary, {
|
|
168
|
+
get(_target, prop) {
|
|
169
|
+
if (prop === FALLBACK_LOCATOR_CANDIDATES) return candidates;
|
|
170
|
+
if (typeof prop !== "string") return primary[prop];
|
|
171
|
+
if (passthroughMetaProperties.has(prop)) return primary[prop];
|
|
172
|
+
switch (prop) {
|
|
173
|
+
case "toString":
|
|
174
|
+
return () => formatLocatorChain(candidates);
|
|
175
|
+
case "all":
|
|
176
|
+
return handleAll(candidates);
|
|
177
|
+
case "_expect":
|
|
178
|
+
return handleExpect(primary, candidates);
|
|
179
|
+
case "count":
|
|
180
|
+
return handleCount(primary, candidates);
|
|
181
|
+
}
|
|
182
|
+
if (unsupported.has(prop)) {
|
|
183
|
+
return () => {
|
|
184
|
+
throw new Error(`FallbackLocator does not support ${prop}`);
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (LOCATOR_CHAIN_METHODS.has(prop)) {
|
|
188
|
+
return handleLocatorChain(prop, candidates);
|
|
189
|
+
}
|
|
190
|
+
const primaryMethod = primary[prop];
|
|
191
|
+
if (typeof primaryMethod !== "function") return primaryMethod;
|
|
192
|
+
if (ACTION_METHODS.has(prop)) {
|
|
193
|
+
return handleAction(prop, primary, candidates, primaryMethod);
|
|
194
|
+
}
|
|
195
|
+
return handleAsyncFallback(prop, primaryMethod, primary, candidates);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
return proxy;
|
|
199
|
+
}
|
|
200
|
+
function getFallbackLocatorCandidates(locator) {
|
|
201
|
+
return locator[FALLBACK_LOCATOR_CANDIDATES] ?? null;
|
|
202
|
+
}
|
|
203
|
+
|
|
30
204
|
// src/utils/pick-field-element.ts
|
|
205
|
+
async function resolveConcreteFieldLocator(elements) {
|
|
206
|
+
const fallbackCandidates = getFallbackLocatorCandidates(elements);
|
|
207
|
+
if (!fallbackCandidates) return elements;
|
|
208
|
+
for (const candidate of fallbackCandidates) {
|
|
209
|
+
try {
|
|
210
|
+
if (await candidate.count() > 0) return candidate;
|
|
211
|
+
} catch {
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return elements;
|
|
215
|
+
}
|
|
31
216
|
async function pickFieldElement(elements) {
|
|
217
|
+
elements = await resolveConcreteFieldLocator(elements);
|
|
32
218
|
const count = await elements.count();
|
|
33
|
-
if (count
|
|
219
|
+
if (count <= 1) return elements.first();
|
|
34
220
|
const candidates = [];
|
|
35
221
|
for (let i = 0; i < count; i++) {
|
|
36
222
|
const el = elements.nth(i);
|
|
@@ -60,7 +246,7 @@ async function pickFieldElement(elements) {
|
|
|
60
246
|
if (isParent !== null) {
|
|
61
247
|
return elements.nth(isParent);
|
|
62
248
|
}
|
|
63
|
-
return elements;
|
|
249
|
+
return elements.first();
|
|
64
250
|
}
|
|
65
251
|
|
|
66
252
|
// src/field/aria-select.ts
|
|
@@ -1744,9 +1930,7 @@ async function setFieldValue(el, value, options) {
|
|
|
1744
1930
|
// fallback (eg contenteditable or will fail)
|
|
1745
1931
|
setFallback
|
|
1746
1932
|
);
|
|
1747
|
-
|
|
1748
|
-
el = await pickFieldElement(el);
|
|
1749
|
-
}
|
|
1933
|
+
el = await pickFieldElement(el);
|
|
1750
1934
|
const tag = await el.evaluate((e) => e.tagName.toLowerCase(), options);
|
|
1751
1935
|
const type = (await el.getAttribute("type", options).catch(
|
|
1752
1936
|
/* v8 ignore next — attribute might be missing or element might have detached during the check */
|
|
@@ -1761,175 +1945,6 @@ async function formatHtml(page) {
|
|
|
1761
1945
|
return String(file);
|
|
1762
1946
|
}
|
|
1763
1947
|
|
|
1764
|
-
// src/fallback-locator.ts
|
|
1765
|
-
var ACTION_METHODS = /* @__PURE__ */ new Set([
|
|
1766
|
-
"blur",
|
|
1767
|
-
"check",
|
|
1768
|
-
"clear",
|
|
1769
|
-
"click",
|
|
1770
|
-
"dblclick",
|
|
1771
|
-
"dispatchEvent",
|
|
1772
|
-
"dragTo",
|
|
1773
|
-
"fill",
|
|
1774
|
-
"focus",
|
|
1775
|
-
"hover",
|
|
1776
|
-
"press",
|
|
1777
|
-
"pressSequentially",
|
|
1778
|
-
"scrollIntoViewIfNeeded",
|
|
1779
|
-
"selectOption",
|
|
1780
|
-
"setChecked",
|
|
1781
|
-
"setInputFiles",
|
|
1782
|
-
"tap",
|
|
1783
|
-
"type",
|
|
1784
|
-
"uncheck"
|
|
1785
|
-
]);
|
|
1786
|
-
var LOCATOR_CHAIN_METHODS = /* @__PURE__ */ new Set([
|
|
1787
|
-
"and",
|
|
1788
|
-
"first",
|
|
1789
|
-
"last",
|
|
1790
|
-
"locator",
|
|
1791
|
-
"nth",
|
|
1792
|
-
"or"
|
|
1793
|
-
]);
|
|
1794
|
-
var NO_WAIT_OPTION_INDEX = {
|
|
1795
|
-
dragTo: 1,
|
|
1796
|
-
fill: 1,
|
|
1797
|
-
press: 1,
|
|
1798
|
-
pressSequentially: 1,
|
|
1799
|
-
selectOption: 1,
|
|
1800
|
-
setInputFiles: 1,
|
|
1801
|
-
type: 1
|
|
1802
|
-
};
|
|
1803
|
-
function buildOrLocator(candidates) {
|
|
1804
|
-
let result = candidates[0];
|
|
1805
|
-
for (const candidate of candidates.slice(1)) {
|
|
1806
|
-
result = result.or(candidate);
|
|
1807
|
-
}
|
|
1808
|
-
return result;
|
|
1809
|
-
}
|
|
1810
|
-
async function firstPresentFallback(candidates) {
|
|
1811
|
-
for (const candidate of candidates.slice(1)) {
|
|
1812
|
-
try {
|
|
1813
|
-
if (await candidate.count() > 0) return candidate;
|
|
1814
|
-
} catch {
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
return null;
|
|
1818
|
-
}
|
|
1819
|
-
function withNoWaitTimeout(method, args) {
|
|
1820
|
-
const next = [...args];
|
|
1821
|
-
const optionIndex = NO_WAIT_OPTION_INDEX[method] ?? 0;
|
|
1822
|
-
const current = next[optionIndex];
|
|
1823
|
-
if (current && typeof current === "object") {
|
|
1824
|
-
next[optionIndex] = { ...current, timeout: 0 };
|
|
1825
|
-
} else {
|
|
1826
|
-
next[optionIndex] = { timeout: 0 };
|
|
1827
|
-
}
|
|
1828
|
-
return next;
|
|
1829
|
-
}
|
|
1830
|
-
function handleExpect(primary, candidates) {
|
|
1831
|
-
return (expression, options) => {
|
|
1832
|
-
if (expression.includes("to.be.visible") || expression.includes("to.be.attached")) {
|
|
1833
|
-
return buildOrLocator(candidates)._expect(expression, options);
|
|
1834
|
-
}
|
|
1835
|
-
return primary._expect(expression, options);
|
|
1836
|
-
};
|
|
1837
|
-
}
|
|
1838
|
-
function handleAll(candidates) {
|
|
1839
|
-
return async () => {
|
|
1840
|
-
const all = [];
|
|
1841
|
-
for (const candidate of candidates) {
|
|
1842
|
-
all.push(...await candidate.all());
|
|
1843
|
-
}
|
|
1844
|
-
return all;
|
|
1845
|
-
};
|
|
1846
|
-
}
|
|
1847
|
-
function handleLocatorChain(prop, candidates) {
|
|
1848
|
-
return (...args) => createFallbackLocator(
|
|
1849
|
-
candidates.map((candidate) => {
|
|
1850
|
-
const method = candidate[prop];
|
|
1851
|
-
return method.apply(candidate, args);
|
|
1852
|
-
})
|
|
1853
|
-
);
|
|
1854
|
-
}
|
|
1855
|
-
function handleCount(primary, candidates) {
|
|
1856
|
-
return async () => {
|
|
1857
|
-
const primaryCount = await primary.count();
|
|
1858
|
-
if (primaryCount > 0) return primaryCount;
|
|
1859
|
-
const fallback = await firstPresentFallback(candidates);
|
|
1860
|
-
if (!fallback) return primaryCount;
|
|
1861
|
-
return fallback.count();
|
|
1862
|
-
};
|
|
1863
|
-
}
|
|
1864
|
-
function handleAction(prop, primary, candidates, primaryMethod) {
|
|
1865
|
-
return async (...args) => {
|
|
1866
|
-
try {
|
|
1867
|
-
return await primaryMethod.apply(primary, args);
|
|
1868
|
-
} catch (error) {
|
|
1869
|
-
const fallback = await firstPresentFallback(candidates);
|
|
1870
|
-
if (!fallback) throw error;
|
|
1871
|
-
const fallbackMethod = fallback[prop];
|
|
1872
|
-
return fallbackMethod.apply(fallback, withNoWaitTimeout(prop, args));
|
|
1873
|
-
}
|
|
1874
|
-
};
|
|
1875
|
-
}
|
|
1876
|
-
function handleAsyncFallback(prop, primaryMethod, primary, candidates) {
|
|
1877
|
-
return (...args) => {
|
|
1878
|
-
const result = primaryMethod.apply(primary, args);
|
|
1879
|
-
if (!result || typeof result !== "object" || typeof result.catch !== "function") {
|
|
1880
|
-
return result;
|
|
1881
|
-
}
|
|
1882
|
-
return result.catch(async (error) => {
|
|
1883
|
-
const fallback = await firstPresentFallback(candidates);
|
|
1884
|
-
if (!fallback) throw error;
|
|
1885
|
-
const fallbackMethod = fallback[prop];
|
|
1886
|
-
return fallbackMethod.apply(fallback, args);
|
|
1887
|
-
});
|
|
1888
|
-
};
|
|
1889
|
-
}
|
|
1890
|
-
function formatLocatorChain(candidates) {
|
|
1891
|
-
const parts = candidates.map((candidate) => candidate.toString());
|
|
1892
|
-
if (parts.length === 0) return "";
|
|
1893
|
-
if (parts.length === 1) return parts[0];
|
|
1894
|
-
return `${parts[0]} {fuzzy}`;
|
|
1895
|
-
}
|
|
1896
|
-
function createFallbackLocator(candidates) {
|
|
1897
|
-
const primary = candidates[0];
|
|
1898
|
-
const unsupported = /* @__PURE__ */ new Set(["filter", "getByRole", "getByLabel"]);
|
|
1899
|
-
const passthroughMetaProperties = /* @__PURE__ */ new Set(["constructor", "__proto__"]);
|
|
1900
|
-
const proxy = new Proxy(primary, {
|
|
1901
|
-
get(_target, prop) {
|
|
1902
|
-
if (typeof prop !== "string") return primary[prop];
|
|
1903
|
-
if (passthroughMetaProperties.has(prop)) return primary[prop];
|
|
1904
|
-
switch (prop) {
|
|
1905
|
-
case "toString":
|
|
1906
|
-
return () => formatLocatorChain(candidates);
|
|
1907
|
-
case "all":
|
|
1908
|
-
return handleAll(candidates);
|
|
1909
|
-
case "_expect":
|
|
1910
|
-
return handleExpect(primary, candidates);
|
|
1911
|
-
case "count":
|
|
1912
|
-
return handleCount(primary, candidates);
|
|
1913
|
-
}
|
|
1914
|
-
if (unsupported.has(prop)) {
|
|
1915
|
-
return () => {
|
|
1916
|
-
throw new Error(`FallbackLocator does not support ${prop}`);
|
|
1917
|
-
};
|
|
1918
|
-
}
|
|
1919
|
-
if (LOCATOR_CHAIN_METHODS.has(prop)) {
|
|
1920
|
-
return handleLocatorChain(prop, candidates);
|
|
1921
|
-
}
|
|
1922
|
-
const primaryMethod = primary[prop];
|
|
1923
|
-
if (typeof primaryMethod !== "function") return primaryMethod;
|
|
1924
|
-
if (ACTION_METHODS.has(prop)) {
|
|
1925
|
-
return handleAction(prop, primary, candidates, primaryMethod);
|
|
1926
|
-
}
|
|
1927
|
-
return handleAsyncFallback(prop, primaryMethod, primary, candidates);
|
|
1928
|
-
}
|
|
1929
|
-
});
|
|
1930
|
-
return proxy;
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
1948
|
// src/fuzzy-locator.ts
|
|
1934
1949
|
function debug(...args) {
|
|
1935
1950
|
if (process.env.LETSRUNIT_DEBUG_FUZZY_LOCATOR === "1") {
|