@letsrunit/playwright 0.19.0 → 0.19.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/index.d.ts +1 -1
- package/dist/index.js +177 -9
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/fallback-locator.ts +195 -0
- package/src/fuzzy-locator.ts +14 -13
package/dist/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ declare function setFieldValue(el: Locator, value: Value, options?: SetOptions):
|
|
|
16
16
|
declare function formatHtml(page: string | Page): Promise<string>;
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Locates an element using Playwright selectors, with
|
|
19
|
+
* Locates an element using Playwright selectors, with ordered runtime fallbacks.
|
|
20
20
|
*/
|
|
21
21
|
declare function fuzzyLocator(page: Page, selector: string): Promise<Locator>;
|
|
22
22
|
|
package/dist/index.js
CHANGED
|
@@ -68746,6 +68746,173 @@ async function formatHtml(page) {
|
|
|
68746
68746
|
return String(file);
|
|
68747
68747
|
}
|
|
68748
68748
|
|
|
68749
|
+
// src/fallback-locator.ts
|
|
68750
|
+
var ACTION_METHODS = /* @__PURE__ */ new Set([
|
|
68751
|
+
"blur",
|
|
68752
|
+
"check",
|
|
68753
|
+
"clear",
|
|
68754
|
+
"click",
|
|
68755
|
+
"dblclick",
|
|
68756
|
+
"dispatchEvent",
|
|
68757
|
+
"dragTo",
|
|
68758
|
+
"fill",
|
|
68759
|
+
"focus",
|
|
68760
|
+
"hover",
|
|
68761
|
+
"press",
|
|
68762
|
+
"pressSequentially",
|
|
68763
|
+
"scrollIntoViewIfNeeded",
|
|
68764
|
+
"selectOption",
|
|
68765
|
+
"setChecked",
|
|
68766
|
+
"setInputFiles",
|
|
68767
|
+
"tap",
|
|
68768
|
+
"type",
|
|
68769
|
+
"uncheck"
|
|
68770
|
+
]);
|
|
68771
|
+
var LOCATOR_CHAIN_METHODS = /* @__PURE__ */ new Set([
|
|
68772
|
+
"and",
|
|
68773
|
+
"first",
|
|
68774
|
+
"last",
|
|
68775
|
+
"locator",
|
|
68776
|
+
"nth",
|
|
68777
|
+
"or"
|
|
68778
|
+
]);
|
|
68779
|
+
var NO_WAIT_OPTION_INDEX = {
|
|
68780
|
+
dragTo: 1,
|
|
68781
|
+
fill: 1,
|
|
68782
|
+
press: 1,
|
|
68783
|
+
pressSequentially: 1,
|
|
68784
|
+
selectOption: 1,
|
|
68785
|
+
setInputFiles: 1,
|
|
68786
|
+
type: 1
|
|
68787
|
+
};
|
|
68788
|
+
function buildOrLocator(candidates) {
|
|
68789
|
+
let result = candidates[0];
|
|
68790
|
+
for (const candidate of candidates.slice(1)) {
|
|
68791
|
+
result = result.or(candidate);
|
|
68792
|
+
}
|
|
68793
|
+
return result;
|
|
68794
|
+
}
|
|
68795
|
+
async function firstPresentFallback(candidates) {
|
|
68796
|
+
for (const candidate of candidates.slice(1)) {
|
|
68797
|
+
try {
|
|
68798
|
+
if (await candidate.count() > 0) return candidate;
|
|
68799
|
+
} catch {
|
|
68800
|
+
}
|
|
68801
|
+
}
|
|
68802
|
+
return null;
|
|
68803
|
+
}
|
|
68804
|
+
function withNoWaitTimeout(method, args) {
|
|
68805
|
+
const next = [...args];
|
|
68806
|
+
const optionIndex = NO_WAIT_OPTION_INDEX[method] ?? 0;
|
|
68807
|
+
const current = next[optionIndex];
|
|
68808
|
+
if (current && typeof current === "object") {
|
|
68809
|
+
next[optionIndex] = { ...current, timeout: 0 };
|
|
68810
|
+
} else {
|
|
68811
|
+
next[optionIndex] = { timeout: 0 };
|
|
68812
|
+
}
|
|
68813
|
+
return next;
|
|
68814
|
+
}
|
|
68815
|
+
function handleExpect(primary, candidates) {
|
|
68816
|
+
return (expression, options) => {
|
|
68817
|
+
if (expression.includes("to.be.visible") || expression.includes("to.be.attached")) {
|
|
68818
|
+
return buildOrLocator(candidates)._expect(expression, options);
|
|
68819
|
+
}
|
|
68820
|
+
return primary._expect(expression, options);
|
|
68821
|
+
};
|
|
68822
|
+
}
|
|
68823
|
+
function handleAll(candidates) {
|
|
68824
|
+
return async () => {
|
|
68825
|
+
const all = [];
|
|
68826
|
+
for (const candidate of candidates) {
|
|
68827
|
+
all.push(...await candidate.all());
|
|
68828
|
+
}
|
|
68829
|
+
return all;
|
|
68830
|
+
};
|
|
68831
|
+
}
|
|
68832
|
+
function handleLocatorChain(prop, candidates) {
|
|
68833
|
+
return (...args) => createFallbackLocator(
|
|
68834
|
+
candidates.map((candidate) => {
|
|
68835
|
+
const method = candidate[prop];
|
|
68836
|
+
return method.apply(candidate, args);
|
|
68837
|
+
})
|
|
68838
|
+
);
|
|
68839
|
+
}
|
|
68840
|
+
function handleCount(primary, candidates) {
|
|
68841
|
+
return async () => {
|
|
68842
|
+
const primaryCount = await primary.count();
|
|
68843
|
+
if (primaryCount > 0) return primaryCount;
|
|
68844
|
+
const fallback = await firstPresentFallback(candidates);
|
|
68845
|
+
if (!fallback) return primaryCount;
|
|
68846
|
+
return fallback.count();
|
|
68847
|
+
};
|
|
68848
|
+
}
|
|
68849
|
+
function handleAction(prop, primary, candidates, primaryMethod) {
|
|
68850
|
+
return async (...args) => {
|
|
68851
|
+
try {
|
|
68852
|
+
return await primaryMethod.apply(primary, args);
|
|
68853
|
+
} catch (error) {
|
|
68854
|
+
const fallback = await firstPresentFallback(candidates);
|
|
68855
|
+
if (!fallback) throw error;
|
|
68856
|
+
const fallbackMethod = fallback[prop];
|
|
68857
|
+
return fallbackMethod.apply(fallback, withNoWaitTimeout(prop, args));
|
|
68858
|
+
}
|
|
68859
|
+
};
|
|
68860
|
+
}
|
|
68861
|
+
function handleAsyncFallback(prop, primaryMethod, primary, candidates) {
|
|
68862
|
+
return (...args) => {
|
|
68863
|
+
const result = primaryMethod.apply(primary, args);
|
|
68864
|
+
if (!result || typeof result !== "object" || typeof result.catch !== "function") {
|
|
68865
|
+
return result;
|
|
68866
|
+
}
|
|
68867
|
+
return result.catch(async (error) => {
|
|
68868
|
+
const fallback = await firstPresentFallback(candidates);
|
|
68869
|
+
if (!fallback) throw error;
|
|
68870
|
+
const fallbackMethod = fallback[prop];
|
|
68871
|
+
return fallbackMethod.apply(fallback, args);
|
|
68872
|
+
});
|
|
68873
|
+
};
|
|
68874
|
+
}
|
|
68875
|
+
function formatLocatorChain(candidates) {
|
|
68876
|
+
const parts = candidates.map((candidate) => candidate.toString());
|
|
68877
|
+
if (parts.length === 0) return "";
|
|
68878
|
+
if (parts.length === 1) return parts[0];
|
|
68879
|
+
return `${parts[0]} {fuzzy}`;
|
|
68880
|
+
}
|
|
68881
|
+
function createFallbackLocator(candidates) {
|
|
68882
|
+
const primary = candidates[0];
|
|
68883
|
+
const unsupported = /* @__PURE__ */ new Set(["filter", "getByRole", "getByLabel"]);
|
|
68884
|
+
const proxy = new Proxy(primary, {
|
|
68885
|
+
get(_target, prop) {
|
|
68886
|
+
if (typeof prop !== "string") return primary[prop];
|
|
68887
|
+
switch (prop) {
|
|
68888
|
+
case "toString":
|
|
68889
|
+
return () => formatLocatorChain(candidates);
|
|
68890
|
+
case "all":
|
|
68891
|
+
return handleAll(candidates);
|
|
68892
|
+
case "_expect":
|
|
68893
|
+
return handleExpect(primary, candidates);
|
|
68894
|
+
case "count":
|
|
68895
|
+
return handleCount(primary, candidates);
|
|
68896
|
+
}
|
|
68897
|
+
if (unsupported.has(prop)) {
|
|
68898
|
+
return () => {
|
|
68899
|
+
throw new Error(`FallbackLocator does not support ${prop}`);
|
|
68900
|
+
};
|
|
68901
|
+
}
|
|
68902
|
+
if (LOCATOR_CHAIN_METHODS.has(prop)) {
|
|
68903
|
+
return handleLocatorChain(prop, candidates);
|
|
68904
|
+
}
|
|
68905
|
+
const primaryMethod = primary[prop];
|
|
68906
|
+
if (typeof primaryMethod !== "function") return primaryMethod;
|
|
68907
|
+
if (ACTION_METHODS.has(prop)) {
|
|
68908
|
+
return handleAction(prop, primary, candidates, primaryMethod);
|
|
68909
|
+
}
|
|
68910
|
+
return handleAsyncFallback(prop, primaryMethod, primary, candidates);
|
|
68911
|
+
}
|
|
68912
|
+
});
|
|
68913
|
+
return proxy;
|
|
68914
|
+
}
|
|
68915
|
+
|
|
68749
68916
|
// src/fuzzy-locator.ts
|
|
68750
68917
|
function debug(...args) {
|
|
68751
68918
|
if (process.env.LETSRUNIT_DEBUG_FUZZY_LOCATOR === "1") {
|
|
@@ -68754,6 +68921,11 @@ function debug(...args) {
|
|
|
68754
68921
|
}
|
|
68755
68922
|
async function fuzzyLocator(page, selector) {
|
|
68756
68923
|
debug("input selector:", selector);
|
|
68924
|
+
const candidates = buildFuzzyCandidates(page, selector);
|
|
68925
|
+
debug("enabled fallbacks:", candidates.length > 1 ? String(candidates.length - 1) : "(none)");
|
|
68926
|
+
return createFallbackLocator(candidates);
|
|
68927
|
+
}
|
|
68928
|
+
function buildFuzzyCandidates(page, selector) {
|
|
68757
68929
|
const primary = page.locator(selector);
|
|
68758
68930
|
const candidates = [
|
|
68759
68931
|
{ name: "relaxNameToHasText", locator: tryRelaxNameToHasText(page, selector) },
|
|
@@ -68762,17 +68934,13 @@ async function fuzzyLocator(page, selector) {
|
|
|
68762
68934
|
{ name: "fieldAlternative", locator: tryFieldAlternative(page, selector) },
|
|
68763
68935
|
{ name: "asField", locator: tryAsField(page, selector) }
|
|
68764
68936
|
];
|
|
68765
|
-
|
|
68766
|
-
const enabled = [];
|
|
68937
|
+
const all = [primary];
|
|
68767
68938
|
for (const candidate of candidates) {
|
|
68768
68939
|
if (!candidate.locator) continue;
|
|
68769
|
-
|
|
68770
|
-
|
|
68940
|
+
debug("enabling fallback:", candidate.name, candidate.locator.toString());
|
|
68941
|
+
all.push(candidate.locator);
|
|
68771
68942
|
}
|
|
68772
|
-
|
|
68773
|
-
const result = combined.first();
|
|
68774
|
-
debug("returning locator:", result.toString());
|
|
68775
|
-
return result;
|
|
68943
|
+
return all;
|
|
68776
68944
|
}
|
|
68777
68945
|
function tryRelaxNameToHasText(page, selector) {
|
|
68778
68946
|
const matchAnyNameFull = selector.match(/^(role=.*)\[name="([^"]+)"i?](.*)$/i);
|
|
@@ -68793,7 +68961,7 @@ function tryRoleNameProximity(page, selector) {
|
|
|
68793
68961
|
const matchRole = selector.match(/^role=(\w+)\s*\[name="([^"]+)"i?](.*)$/i);
|
|
68794
68962
|
if (!matchRole) return null;
|
|
68795
68963
|
const [, role, name, rest] = matchRole;
|
|
68796
|
-
const proximitySelector = `text=${name} >>
|
|
68964
|
+
const proximitySelector = `text=${name} >> xpath=following-sibling::* >> role=${role}${rest}`;
|
|
68797
68965
|
return page.locator(proximitySelector);
|
|
68798
68966
|
}
|
|
68799
68967
|
function tryFieldAlternative(page, selector) {
|