From 90a1f095fea9eed2c276c0f7610911cad57bc978 Mon Sep 17 00:00:00 2001 From: suchintan <3853670+suchintan@users.noreply.github.com> Date: Sat, 15 Aug 2026 19:01:48 +0000 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=94=84=20synced=20local=20'python/'?= =?UTF-8?q?=20with=20remote=20'python/'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Skyvern-AI/rustwright-cloud/pull/213 --- python/rustwright/sync_api.py | 136 ++++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 47 deletions(-) diff --git a/python/rustwright/sync_api.py b/python/rustwright/sync_api.py index be8d50a..32e0026 100644 --- a/python/rustwright/sync_api.py +++ b/python/rustwright/sync_api.py @@ -792,14 +792,14 @@ def _clock_shim_script(control_name: str) -> str: } return false; }; -const isHiddenForAria = node => { +const isHiddenForAria = (node, style) => { if (!node || node.nodeType !== 1) return true; if (node.hasAttribute('hidden')) return true; if (String(node.getAttribute('aria-hidden') || '').toLowerCase() === 'true') return true; if (hiddenByClosedDetails(node)) return true; const view = (node.ownerDocument && node.ownerDocument.defaultView) || window; - const style = view.getComputedStyle(node); - return style.display === 'none' || style.visibility === 'hidden'; + const computedStyle = style || view.getComputedStyle(node); + return computedStyle.display === 'none' || computedStyle.visibility === 'hidden'; }; const textValue = value => normalize(String(value ?? '')); const rawScalar = value => { @@ -818,10 +818,34 @@ def _clock_shim_script(control_name: str) -> str: if (/^".*"$/.test(normalized)) return normalized; return needsQuotedYamlScalar(normalized) ? JSON.stringify(normalized) : normalized; }; -const urlScalar = value => { - const normalized = textValue(value); - if (!normalized) return JSON.stringify(normalized); - return normalized.startsWith('#') ? JSON.stringify(normalized) : normalized; +const yamlEscapeValueIfNeeded = value => { + const raw = String(value ?? ''); + if (!yamlStringNeedsQuotes(raw)) return raw; + return '"' + raw.replace(/[\\"\x00-\x1f\x7f-\x9f]/g, char => { + if (char === '\\') return '\\\\'; + if (char === '"') return '\\"'; + if (char === '\b') return '\\b'; + if (char === '\f') return '\\f'; + if (char === '\n') return '\\n'; + if (char === '\r') return '\\r'; + if (char === '\t') return '\\t'; + return '\\x' + char.charCodeAt(0).toString(16).padStart(2, '0'); + }) + '"'; +}; +const yamlStringNeedsQuotes = value => { + const raw = String(value ?? ''); + if (!raw) return true; + if (/^\s|\s$/.test(raw)) return true; + if (/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/.test(raw)) return true; + if (/^-/.test(raw)) return true; + if (/[\n:](\s|$)/.test(raw)) return true; + if (/\s#/.test(raw)) return true; + if (/[\n\r]/.test(raw)) return true; + if (/^[&*\],?!>|@"'#%]/.test(raw)) return true; + if (/[{}`]/.test(raw)) return true; + if (/^\[/.test(raw)) return true; + if (!isNaN(Number(raw)) || ['y', 'n', 'yes', 'no', 'true', 'false', 'on', 'off', 'null'].includes(raw.toLowerCase())) return true; + return false; }; const roleValueLabelPattern = /^[a-z]+(?: "[^"]*")?(?: \[[^\]]+\])*: /; const yamlLabel = value => { @@ -835,20 +859,33 @@ def _clock_shim_script(control_name: str) -> str: .filter(Boolean) .join(' '); const quoted = value => JSON.stringify(textValue(value)); +const quotedName = value => { + const normalized = textValue(value); + return normalized.startsWith('/') && normalized.endsWith('/') ? normalized : JSON.stringify(normalized); +}; const snapshotDescendantText = (node, root) => { if (!node) return ''; if (node.nodeType === Node.TEXT_NODE) return node.textContent || ''; - if (node.nodeType !== Node.ELEMENT_NODE || (node !== root && isHiddenForAria(node))) return ''; + if (node.nodeType !== Node.ELEMENT_NODE) return ''; const tag = node.tagName || ''; - if (tag === 'IMG') return node.getAttribute('alt') || node.getAttribute('title') || ''; - if (String(tag).toLowerCase() === 'svg') { - const title = node.querySelector('title'); - return title ? title.textContent || '' : node.getAttribute('title') || ''; - } - if (String(node.getAttribute('role') || '').trim().split(/\s+/).filter(Boolean)[0] === 'img') { - return node.getAttribute('aria-label') || node.getAttribute('title') || ''; + const view = (node.ownerDocument && node.ownerDocument.defaultView) || window; + const style = node === root ? null : view.getComputedStyle(node); + const hidden = node !== root && isHiddenForAria(node, style); + let text = ''; + if (!hidden) { + if (tag === 'IMG') text = node.getAttribute('alt') || node.getAttribute('title') || ''; + else if (String(tag).toLowerCase() === 'svg') { + const title = node.querySelector('title'); + text = title ? title.textContent || '' : node.getAttribute('title') || ''; + } else if (String(node.getAttribute('role') || '').trim().split(/\s+/).filter(Boolean)[0] === 'img') { + text = node.getAttribute('aria-label') || node.getAttribute('title') || ''; + } else { + text = Array.from(node.childNodes || []).map(child => snapshotDescendantText(child, root)).join(''); + } } - return Array.from(node.childNodes || []).map(child => snapshotDescendantText(child, root)).join(' '); + if (node === root) return text; + const display = style.display || 'inline'; + return display !== 'inline' || tag === 'BR' ? ` ${text} ` : text; }; const labelText = node => { if (!node) return ''; @@ -1345,7 +1382,7 @@ def _clock_shim_script(control_name: str) -> str: let label = role; if (name && !textSemanticRoles.has(role)) { - label += ` ${quoted(name)}`; + label += ` ${quotedName(name)}`; } label += stateSuffix(node, role); if (value) { @@ -1360,7 +1397,7 @@ def _clock_shim_script(control_name: str) -> str: if (node.hasAttribute('href')) { const href = node.getAttribute('href') || ''; if (!result[0].label.endsWith(':')) result[0].label += ':'; - result[0].children = [{ kind: 'text', text: `/url: ${urlScalar(href)}` }, ...result[0].children]; + result[0].children = [{ kind: 'text', text: `/url: ${yamlEscapeValueIfNeeded(href)}` }, ...result[0].children]; } } return result; @@ -20548,7 +20585,7 @@ def _try_fast_simple_css_aria_snapshot( @functools.lru_cache(maxsize=1) def _aria_snapshot_helper_function_script() -> str: prelude = r""" -const normalize = value => String(value ?? '').replace(/\s+/g, ' ').trim(); +const normalize = value => String(value ?? '').replace(/[\u200b\u00ad]/g, '').replace(/\s+/g, ' ').trim(); const referencedText = (el, attribute) => { const doc = el.ownerDocument || document; const referencedControlText = node => { @@ -20941,7 +20978,7 @@ def _simple_role_fast_path_payload(self) -> Optional[dict[str, Any]]: @functools.lru_cache(maxsize=64) def _fast_simple_role_script(action_body: str) -> str: return f"""(payload) => {{ -const normalize = value => String(value ?? '').replace(/\\s+/g, ' ').trim(); +const normalize = value => String(value ?? '').replace(/[\\u200b\\u00ad]/g, '').replace(/\\s+/g, ' ').trim(); const includesText = (value, needle, exact) => {{ const raw = String(value ?? ''); if (needle && typeof needle === 'object' && needle.kind === 'regex') {{ @@ -21041,36 +21078,42 @@ def _fast_simple_role_script(action_body: str) -> str: }}) .join(' '); }}; -const hiddenForName = (node, root) => {{ +const hiddenForName = (node, root, style) => {{ if (!node || node.nodeType !== 1 || node === root) return false; if (node.hasAttribute('hidden')) return true; if (String(node.getAttribute('aria-hidden') || '').toLowerCase() === 'true') return true; - const view = (node.ownerDocument && node.ownerDocument.defaultView) || window; - const style = view.getComputedStyle(node); return style.visibility === 'hidden' || style.display === 'none'; }}; const descendantText = (node, root) => {{ if (!node) return ''; if (node.nodeType === Node.TEXT_NODE) return node.textContent || ''; - if (node.nodeType !== Node.ELEMENT_NODE || hiddenForName(node, root)) return ''; + if (node.nodeType !== Node.ELEMENT_NODE) return ''; const tag = node.tagName || ''; - const type = String(node.getAttribute('type') || '').toLowerCase(); - if (tag === 'IMG') return node.getAttribute('alt') || node.getAttribute('title') || ''; - if (String(tag).toLowerCase() === 'svg') {{ - const title = node.querySelector('title'); - return title ? title.textContent || '' : ''; - }} - if (explicitRoleOf(node) === 'img') return node.getAttribute('aria-label') || node.getAttribute('title') || ''; - if (tag === 'INPUT' && type === 'image') return node.getAttribute('alt') || node.getAttribute('title') || 'Submit'; - if (tag === 'INPUT' && type === 'file') return 'Choose File'; - if (tag === 'INPUT' && ['button', 'submit', 'reset'].includes(type)) {{ - if (node.value) return node.value; - if (node.getAttribute('title')) return node.getAttribute('title') || ''; - if (type === 'submit') return 'Submit'; - if (type === 'reset') return 'Reset'; - return ''; + const view = (node.ownerDocument && node.ownerDocument.defaultView) || window; + const style = node === root ? null : view.getComputedStyle(node); + const hidden = hiddenForName(node, root, style); + let text = ''; + if (!hidden) {{ + const type = String(node.getAttribute('type') || '').toLowerCase(); + if (tag === 'IMG') text = node.getAttribute('alt') || node.getAttribute('title') || ''; + else if (String(tag).toLowerCase() === 'svg') {{ + const title = node.querySelector('title'); + text = title ? title.textContent || '' : ''; + }} else if (explicitRoleOf(node) === 'img') {{ + text = node.getAttribute('aria-label') || node.getAttribute('title') || ''; + }} else if (tag === 'INPUT' && type === 'image') {{ + text = node.getAttribute('alt') || node.getAttribute('title') || 'Submit'; + }} else if (tag === 'INPUT' && type === 'file') {{ + text = 'Choose File'; + }} else if (tag === 'INPUT' && ['button', 'submit', 'reset'].includes(type)) {{ + text = node.value || node.getAttribute('title') || (type === 'submit' ? 'Submit' : type === 'reset' ? 'Reset' : ''); + }} else {{ + text = Array.from(node.childNodes || []).map(child => descendantText(child, root)).join(''); + }} }} - return Array.from(node.childNodes || []).map(child => descendantText(child, root)).join(' '); + if (node === root) return text; + const display = style.display || 'inline'; + return display !== 'inline' || tag === 'BR' ? ` ${{text}} ` : text; }}; const explicitAccessibleName = el => normalize(referencedText(el, 'aria-labelledby') || el.getAttribute('aria-label') || ''); const presentationalRoleOf = el => {{ @@ -21636,7 +21679,7 @@ def _try_fast_simple_text_evaluate_all(self, expression: str, arg: Any, *, metho for (const el of allElements) {{ if (el.shadowRoot) return {{ ok: false, type: 'fallback' }}; }} - const normalize = value => String(value ?? '').replace(/\\s+/g, ' ').trim(); + const normalize = value => String(value ?? '').replace(/[\\u200b\\u00ad]/g, '').replace(/\\s+/g, ' ').trim(); const elementText = node => {{ if (!node) return ''; if (node.nodeType === Node.TEXT_NODE) return node.textContent || ''; @@ -21659,8 +21702,8 @@ def _try_fast_simple_text_evaluate_all(self, expression: str, arg: Any, *, metho regex = new RegExp(String(matcher.pattern || ''), String(matcher.flags || '')); }} const matchesText = text => {{ + if (regex) return regex.test(text); const normalized = normalize(text); - if (regex) return regex.test(normalized); const needle = normalize(matcher); if (payload.exact) return normalized === needle; return normalized.toLowerCase().includes(needle.toLowerCase()); @@ -22966,7 +23009,7 @@ def _try_fast_named_button_role_dom_click(self, *, timeout: Optional[float]) -> } result = self._evaluate_simple_css_fast_path( """(payload) => { -const normalize = value => String(value ?? '').replace(/\\s+/g, ' ').trim(); +const normalize = value => String(value ?? '').replace(/[\\u200b\\u00ad]/g, '').replace(/\\s+/g, ' ').trim(); const includesText = (value, needle, exact) => { const left = normalize(value); const right = normalize(needle); @@ -23150,7 +23193,7 @@ def _fast_label_control_script(action_body: str) -> str: for (let i = 0; i < allElements.length; i++) {{ if (allElements[i].shadowRoot) return {{ ok: false, type: 'fallback' }}; }} -const normalize = value => String(value ?? '').replace(/\\s+/g, ' ').trim(); +const normalize = value => String(value ?? '').replace(/[\\u200b\\u00ad]/g, '').replace(/\\s+/g, ' ').trim(); const includesText = (value, needle, exact) => {{ const raw = String(value ?? ''); if (needle && typeof needle === 'object' && needle.kind === 'regex') {{ @@ -23277,10 +23320,9 @@ def _fast_placeholder_control_script(action_body: str) -> str: for (let i = 0; i < allElements.length; i++) {{ if (allElements[i].shadowRoot) return {{ ok: false, type: 'fallback' }}; }} -const normalize = value => String(value ?? '').replace(/\\s+/g, ' ').trim(); const includesText = (value, needle, exact) => {{ - const left = normalize(value); - const right = normalize(needle); + const left = String(value ?? ''); + const right = String(needle ?? ''); return exact ? left === right : left.toLowerCase().includes(right.toLowerCase()); }}; const visible = el => {{ From c869b1baff7e11b7e27b1fa88936df84933c0e76 Mon Sep 17 00:00:00 2001 From: suchintan <3853670+suchintan@users.noreply.github.com> Date: Sat, 15 Aug 2026 19:01:48 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=94=84=20synced=20local=20'src/'=20wi?= =?UTF-8?q?th=20remote=20'src/'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Skyvern-AI/rustwright-cloud/pull/213 --- src/lib.rs | 164 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 93 insertions(+), 71 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d0af95c..205a10e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -633,7 +633,7 @@ return new Promise(resolve => { "#; const LOCATOR_ASSERTION_PROBE_TEMPLATE: &str = r#" const matcher = __MATCHER__; -const normalizeAssertionText = value => String(value ?? '').replace(/\s+/g, ' ').trim(); +const normalizeAssertionText = value => String(value ?? '').replace(/[\u200b\u00ad]/g, '').replace(/\s+/g, ' ').trim(); const assertionRegExp = expected => { try { let flags = String(expected.flags || ''); @@ -46905,7 +46905,7 @@ fn fast_css_locator_script( "# } else if needs_common_accessibility { r#" - const normalize = value => String(value ?? '').replace(/\s+/g, ' ').trim(); + const normalize = value => String(value ?? '').replace(/[\u200b\u00ad]/g, '').replace(/\s+/g, ' ').trim(); const fastAccessibilityKnownRoles = new Set([ 'alert', 'alertdialog', 'application', 'article', 'banner', 'blockquote', 'button', 'caption', 'cell', 'checkbox', 'code', 'columnheader', 'combobox', 'complementary', @@ -46925,36 +46925,42 @@ fn fast_css_locator_script( } return ''; }; - const hiddenForName = (node, root) => { + const hiddenForName = (node, root, style) => { if (!node || node.nodeType !== 1 || node === root) return false; if (node.hasAttribute('hidden')) return true; if (String(node.getAttribute('aria-hidden') || '').toLowerCase() === 'true') return true; - const view = (node.ownerDocument && node.ownerDocument.defaultView) || window; - const style = view.getComputedStyle(node); return style.visibility === 'hidden' || style.display === 'none'; }; const descendantText = (node, root) => { if (!node) return ''; if (node.nodeType === Node.TEXT_NODE) return node.textContent || ''; - if (node.nodeType !== Node.ELEMENT_NODE || hiddenForName(node, root)) return ''; + if (node.nodeType !== Node.ELEMENT_NODE) return ''; const tag = node.tagName || ''; - const type = String(node.getAttribute('type') || '').toLowerCase(); - if (tag === 'IMG') return node.getAttribute('alt') || node.getAttribute('title') || ''; - if (String(tag).toLowerCase() === 'svg') { - const title = node.querySelector('title'); - return title ? title.textContent || '' : node.getAttribute('title') || ''; - } - if (explicitRoleOf(node) === 'img') return node.getAttribute('aria-label') || node.getAttribute('title') || ''; - if (tag === 'INPUT' && type === 'image') return node.getAttribute('alt') || node.getAttribute('title') || 'Submit'; - if (tag === 'INPUT' && type === 'file') return 'Choose File'; - if (tag === 'INPUT' && ['button', 'submit', 'reset'].includes(type)) { - if (node.value) return node.value; - if (node.getAttribute('title')) return node.getAttribute('title') || ''; - if (type === 'submit') return 'Submit'; - if (type === 'reset') return 'Reset'; - return ''; + const view = (node.ownerDocument && node.ownerDocument.defaultView) || window; + const style = node === root ? null : view.getComputedStyle(node); + const hidden = hiddenForName(node, root, style); + let text = ''; + if (!hidden) { + const type = String(node.getAttribute('type') || '').toLowerCase(); + if (tag === 'IMG') text = node.getAttribute('alt') || node.getAttribute('title') || ''; + else if (String(tag).toLowerCase() === 'svg') { + const title = node.querySelector('title'); + text = title ? title.textContent || '' : node.getAttribute('title') || ''; + } else if (explicitRoleOf(node) === 'img') { + text = node.getAttribute('aria-label') || node.getAttribute('title') || ''; + } else if (tag === 'INPUT' && type === 'image') { + text = node.getAttribute('alt') || node.getAttribute('title') || 'Submit'; + } else if (tag === 'INPUT' && type === 'file') { + text = 'Choose File'; + } else if (tag === 'INPUT' && ['button', 'submit', 'reset'].includes(type)) { + text = node.value || node.getAttribute('title') || (type === 'submit' ? 'Submit' : type === 'reset' ? 'Reset' : ''); + } else { + text = Array.from(node.childNodes || []).map(child => descendantText(child, root)).join(''); + } } - return Array.from(node.childNodes || []).map(child => descendantText(child, root)).join(' '); + if (node === root) return text; + const display = style.display || 'inline'; + return display !== 'inline' || tag === 'BR' ? ` ${text} ` : text; }; const referencedText = (el, attribute) => { const doc = el.ownerDocument || document; @@ -47040,37 +47046,43 @@ fn fast_css_locator_script( } return ''; }; - const normalize = value => String(value ?? '').replace(/\s+/g, ' ').trim(); - const hiddenForName = (node, root) => { + const normalize = value => String(value ?? '').replace(/[\u200b\u00ad]/g, '').replace(/\s+/g, ' ').trim(); + const hiddenForName = (node, root, style) => { if (!node || node.nodeType !== 1 || node === root) return false; if (node.hasAttribute('hidden')) return true; if (String(node.getAttribute('aria-hidden') || '').toLowerCase() === 'true') return true; - const view = (node.ownerDocument && node.ownerDocument.defaultView) || window; - const style = view.getComputedStyle(node); return style.visibility === 'hidden' || style.display === 'none'; }; const descendantText = (node, root) => { if (!node) return ''; if (node.nodeType === Node.TEXT_NODE) return node.textContent || ''; - if (node.nodeType !== Node.ELEMENT_NODE || hiddenForName(node, root)) return ''; + if (node.nodeType !== Node.ELEMENT_NODE) return ''; const tag = node.tagName || ''; - const type = String(node.getAttribute('type') || '').toLowerCase(); - if (tag === 'IMG') return node.getAttribute('alt') || node.getAttribute('title') || ''; - if (String(tag).toLowerCase() === 'svg') { - const title = node.querySelector('title'); - return title ? title.textContent || '' : ''; - } - if (explicitRoleOf(node) === 'img') return node.getAttribute('aria-label') || node.getAttribute('title') || ''; - if (tag === 'INPUT' && type === 'image') return node.getAttribute('alt') || node.getAttribute('title') || 'Submit'; - if (tag === 'INPUT' && type === 'file') return 'Choose File'; - if (tag === 'INPUT' && ['button', 'submit', 'reset'].includes(type)) { - if (node.value) return node.value; - if (node.getAttribute('title')) return node.getAttribute('title') || ''; - if (type === 'submit') return 'Submit'; - if (type === 'reset') return 'Reset'; - return ''; + const view = (node.ownerDocument && node.ownerDocument.defaultView) || window; + const style = node === root ? null : view.getComputedStyle(node); + const hidden = hiddenForName(node, root, style); + let text = ''; + if (!hidden) { + const type = String(node.getAttribute('type') || '').toLowerCase(); + if (tag === 'IMG') text = node.getAttribute('alt') || node.getAttribute('title') || ''; + else if (String(tag).toLowerCase() === 'svg') { + const title = node.querySelector('title'); + text = title ? title.textContent || '' : node.getAttribute('title') || ''; + } else if (explicitRoleOf(node) === 'img') { + text = node.getAttribute('aria-label') || node.getAttribute('title') || ''; + } else if (tag === 'INPUT' && type === 'image') { + text = node.getAttribute('alt') || node.getAttribute('title') || 'Submit'; + } else if (tag === 'INPUT' && type === 'file') { + text = 'Choose File'; + } else if (tag === 'INPUT' && ['button', 'submit', 'reset'].includes(type)) { + text = node.value || node.getAttribute('title') || (type === 'submit' ? 'Submit' : type === 'reset' ? 'Reset' : ''); + } else { + text = Array.from(node.childNodes || []).map(child => descendantText(child, root)).join(''); + } } - return Array.from(node.childNodes || []).map(child => descendantText(child, root)).join(' '); + if (node === root) return text; + const display = style.display || 'inline'; + return display !== 'inline' || tag === 'BR' ? ` ${text} ` : text; }; const referencedText = (el, attribute) => { const doc = el.ownerDocument || document; @@ -47376,7 +47388,7 @@ fn locator_script_for_root( const index = __INDEX__; const locatorRoot = __LOCATOR_ROOT__; - const normalize = value => String(value ?? '').replace(/\s+/g, ' ').trim(); + const normalize = value => String(value ?? '').replace(/[\u200b\u00ad]/g, '').replace(/\s+/g, ' ').trim(); const includesText = (value, needle, exact) => { const raw = String(value ?? ''); if (needle && typeof needle === 'object' && needle.kind === 'regex') { @@ -47390,6 +47402,14 @@ fn locator_script_for_root( const right = normalize(needle); return exact ? left === right : left.toLowerCase().includes(right.toLowerCase()); }; + const includesRawText = (value, needle, exact) => { + if (needle && typeof needle === 'object' && needle.kind === 'regex') { + return includesText(value, needle, exact); + } + const left = String(value ?? ''); + const right = String(needle ?? ''); + return exact ? left === right : left.toLowerCase().includes(right.toLowerCase()); + }; const isEmptyStringTextMatcher = needle => ( typeof needle === 'string' && normalize(needle) === '' ); @@ -47517,40 +47537,42 @@ fn locator_script_for_root( }) .join(' '); }; - const hiddenForName = (node, root) => { + const hiddenForName = (node, root, style) => { if (!node || node.nodeType !== 1 || node === root) return false; if (node.hasAttribute('hidden')) return true; if (String(node.getAttribute('aria-hidden') || '').toLowerCase() === 'true') return true; - const view = (node.ownerDocument && node.ownerDocument.defaultView) || window; - const style = view.getComputedStyle(node); return style.visibility === 'hidden' || style.display === 'none'; }; const descendantText = (node, root) => { if (!node) return ''; if (node.nodeType === Node.TEXT_NODE) return node.textContent || ''; - if (node.nodeType !== Node.ELEMENT_NODE || hiddenForName(node, root)) return ''; + if (node.nodeType !== Node.ELEMENT_NODE) return ''; const tag = node.tagName || ''; - const type = String(node.getAttribute('type') || '').toLowerCase(); - if (tag === 'IMG') return node.getAttribute('alt') || node.getAttribute('title') || ''; - if (String(tag).toLowerCase() === 'svg') { - const title = node.querySelector('title'); - return title ? title.textContent || '' : ''; - } - if (explicitRoleOf(node) === 'img') return node.getAttribute('aria-label') || node.getAttribute('title') || ''; - if (tag === 'INPUT' && type === 'image') { - return node.getAttribute('alt') || node.getAttribute('title') || 'Submit'; - } - if (tag === 'INPUT' && type === 'file') { - return 'Choose File'; - } - if (tag === 'INPUT' && ['button', 'submit', 'reset'].includes(type)) { - if (node.value) return node.value; - if (node.getAttribute('title')) return node.getAttribute('title') || ''; - if (type === 'submit') return 'Submit'; - if (type === 'reset') return 'Reset'; - return ''; + const view = (node.ownerDocument && node.ownerDocument.defaultView) || window; + const style = node === root ? null : view.getComputedStyle(node); + const hidden = hiddenForName(node, root, style); + let text = ''; + if (!hidden) { + const type = String(node.getAttribute('type') || '').toLowerCase(); + if (tag === 'IMG') text = node.getAttribute('alt') || node.getAttribute('title') || ''; + else if (String(tag).toLowerCase() === 'svg') { + const title = node.querySelector('title'); + text = title ? title.textContent || '' : ''; + } else if (explicitRoleOf(node) === 'img') { + text = node.getAttribute('aria-label') || node.getAttribute('title') || ''; + } else if (tag === 'INPUT' && type === 'image') { + text = node.getAttribute('alt') || node.getAttribute('title') || 'Submit'; + } else if (tag === 'INPUT' && type === 'file') { + text = 'Choose File'; + } else if (tag === 'INPUT' && ['button', 'submit', 'reset'].includes(type)) { + text = node.value || node.getAttribute('title') || (type === 'submit' ? 'Submit' : type === 'reset' ? 'Reset' : ''); + } else { + text = Array.from(node.childNodes || []).map(child => descendantText(child, root)).join(''); + } } - return Array.from(node.childNodes || []).map(child => descendantText(child, root)).join(' '); + if (node === root) return text; + const display = style.display || 'inline'; + return display !== 'inline' || tag === 'BR' ? ` ${text} ` : text; }; const explicitAccessibleName = el => normalize(referencedText(el, 'aria-labelledby') || el.getAttribute('aria-label') || ''); const computeAccessibleName = el => { @@ -48154,13 +48176,13 @@ fn locator_script_for_root( return element ? [element] : []; } if (current.kind === 'placeholder') { - return queryAllDeep(root, '[placeholder]').filter(el => includesText(el.getAttribute('placeholder'), current.value, current.exact)); + return queryAllDeep(root, '[placeholder]').filter(el => includesRawText(el.getAttribute('placeholder'), current.value, current.exact)); } if (current.kind === 'alt') { - return queryAllDeep(root, '[alt]').filter(el => includesText(el.getAttribute('alt'), current.value, current.exact)); + return queryAllDeep(root, '[alt]').filter(el => includesRawText(el.getAttribute('alt'), current.value, current.exact)); } if (current.kind === 'title') { - return queryAllDeep(root, '[title]').filter(el => includesText(el.getAttribute('title'), current.value, current.exact)); + return queryAllDeep(root, '[title]').filter(el => includesRawText(el.getAttribute('title'), current.value, current.exact)); } if (current.kind === 'label') { const labelledByMatches = el => { From 01c210427be0d7f117777d011486bb647de239da Mon Sep 17 00:00:00 2001 From: suchintan <3853670+suchintan@users.noreply.github.com> Date: Sat, 15 Aug 2026 19:01:48 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=94=84=20synced=20local=20'tests/'=20?= =?UTF-8?q?with=20remote=20'tests/'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/Skyvern-AI/rustwright-cloud/pull/213 --- tests/fixtures/swagger_operation_buttons.html | 20 +++ tests/test_rustwright_sync_api.py | 152 ++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 tests/fixtures/swagger_operation_buttons.html diff --git a/tests/fixtures/swagger_operation_buttons.html b/tests/fixtures/swagger_operation_buttons.html new file mode 100644 index 0000000..54dd2a2 --- /dev/null +++ b/tests/fixtures/swagger_operation_buttons.html @@ -0,0 +1,20 @@ + + +
+ +A\u200bB
') + ids = "(els) => els.map(el => el.id)" + + assert page.get_by_text(re.compile("AB")).evaluate_all(ids) == [] + assert page.get_by_text(re.compile("A\u200bB")).evaluate_all(ids) == ["zero-width"] + assert page.get_by_text("AB", exact=True).evaluate_all(ids) == ["zero-width"] + + +def test_aria_snapshot_urls_match_upstream_yaml_quoting(page): + page.set_content('Link') + link = page.locator("#link") + cases = [ + ("page #fragment", '"page #fragment"'), + ("?relative", '"?relative"'), + ("%rel", '"%rel"'), + ("123", '"123"'), + ("true", '"true"'), + ("{x}", '"{x}"'), + ("`tick", '"`tick"'), + ("line\nbreak", '"line\\nbreak"'), + ("\u00a0", '"\u00a0"'), + ("https://example.test/A\u200bB", "https://example.test/A\u200bB"), + ] + + for value, serialized in cases: + link.evaluate('(el, href) => el.setAttribute("href", href)', value) + assert link.aria_snapshot() == ( + '- link "Link":\n' + f" - /url: {serialized}" + ) + + +def test_accessibility_and_text_assertions_normalize_zero_width_characters(page): + zero_width = "A\u200bB\u00adC" + page.set_content( + f""" + + + {zero_width} + + {zero_width} +