fix(selectors): emit valid XPath 1.0 string literals via concat()

Both the Go translator and the V8 runtime escaped " by prepending \,
which XPath 1.0 doesn't accept (its string literals have no escape
syntax). A `text:` value containing a quote produced malformed XPath
that chromedp/document.evaluate rejected.

Use the standard concat() composition: when the value contains both
' and ", split on " and join with `, '"', ` so each fragment is
wrapped in single or double quotes individually.
This commit is contained in:
pj committed 2026-05-03 11:00:23 +07:00
1 parent a69bac7abd
commit 372fd23d87
3 files changed
+40 -5

No files matched your search

+12 -2
View File
@@ -111,12 +111,22 @@ function selectorFromObject(selector: Record<string, string | boolean | undefine
}
if (textValue !== undefined && parts.length === 0) {
return {
xpath: `//*[normalize-space(text())="${textValue.replace(/"/g, '\\"')}"]`,
xpath: `//*[normalize-space(text())=${xpathStringLiteral(textValue)}]`,
};
}
return { css: parts.join("") };
}
// xpathStringLiteral wraps the value in a valid XPath 1.0 string literal.
// XPath 1.0 has no escape syntax, so a value containing both ' and " must be
// composed via concat().
function xpathStringLiteral(value: string): string {
if (!value.includes('"')) return `"${value}"`;
if (!value.includes("'")) return `'${value}'`;
const parts = value.split('"');
return `concat(${parts.map((p) => `"${p}"`).join(`, '"', `)})`;
}
function selectorFromString(selector: string): { css?: string; xpath?: string } {
const colon = selector.indexOf(":");
if (colon <= 0) {
@@ -125,7 +135,7 @@ function selectorFromString(selector: string): { css?: string; xpath?: string }
const kind = selector.slice(0, colon);
const value = selector.slice(colon + 1);
if (kind === "text") {
return { xpath: `//*[normalize-space(text())="${value.replace(/"/g, '\\"')}"]` };
return { xpath: `//*[normalize-space(text())=${xpathStringLiteral(value)}]` };
}
if (kind === "descPrefix") {
return { css: `[aria-label^="${cssEscape(value)}"]` };