///|
/// Locator methods
///|
/// Filter locator
pub fn Locator::filter(
self : Locator,
has? : Locator,
has_not? : Locator,
has_text? : String,
has_not_text? : String,
) -> Locator {
consume((has, has_not, has_not_text))
let state = self.state.val
let selector = state.selector
let new_has_text = match has_text {
Some(text) => Some(text)
None => state.has_text
}
let new_has_text_exact = state.has_text_exact
let new_has_not_text = match has_not_text {
Some(text) => Some(text)
None => state.has_not_text
}
let new_state : LocatorState = {
page: state.page,
selector,
index: state.index,
has_text: new_has_text,
has_text_exact: new_has_text_exact,
has_not_text: new_has_not_text,
label_text: state.label_text,
label_text_exact: state.label_text_exact,
label_scope_selector: state.label_scope_selector,
}
{ state: { val: new_state } }
}
///|
/// Get first element
pub fn Locator::first(self : Locator) -> Locator {
let state = self.state.val
let new_state : LocatorState = {
page: state.page,
selector: state.selector,
index: Some(0),
has_text: state.has_text,
has_text_exact: state.has_text_exact,
has_not_text: state.has_not_text,
label_text: state.label_text,
label_text_exact: state.label_text_exact,
label_scope_selector: state.label_scope_selector,
}
{ state: { val: new_state } }
}
///|
/// Get last element
pub fn Locator::last(self : Locator) -> Locator {
let state = self.state.val
let new_state : LocatorState = {
page: state.page,
selector: state.selector,
index: Some(-1),
has_text: state.has_text,
has_text_exact: state.has_text_exact,
has_not_text: state.has_not_text,
label_text: state.label_text,
label_text_exact: state.label_text_exact,
label_scope_selector: state.label_scope_selector,
}
{ state: { val: new_state } }
}
///|
/// Get nth element (0-indexed)
pub fn Locator::nth(self : Locator, index : Int) -> Locator {
let state = self.state.val
let new_state : LocatorState = {
page: state.page,
selector: state.selector,
index: Some(index),
has_text: state.has_text,
has_text_exact: state.has_text_exact,
has_not_text: state.has_not_text,
label_text: state.label_text,
label_text_exact: state.label_text_exact,
label_scope_selector: state.label_scope_selector,
}
{ state: { val: new_state } }
}
///|
/// Get locator within this locator
pub fn Locator::locator(self : Locator, selector : String) -> Locator {
let state = self.state.val
let combined = state.selector + " " + selector
let new_state : LocatorState = {
page: state.page,
selector: combined,
index: None,
has_text: None,
has_text_exact: false,
has_not_text: None,
label_text: None,
label_text_exact: false,
label_scope_selector: None,
}
{ state: { val: new_state } }
}
///|
/// Get by role within this locator
pub fn Locator::get_by_role(
self : Locator,
role : String,
name? : String,
exact? : Bool,
) -> Locator {
consume(exact)
let selector = self.state.val.selector +
" [role=" +
js_string_literal(role) +
"]"
let base = Locator::from_selector(self.state.val.page, selector)
match name {
Some(text) => base.get_by_text(text, exact?)
None => base
}
}
///|
/// Get by text within this locator
pub fn Locator::get_by_text(
self : Locator,
text : String,
exact? : Bool,
) -> Locator {
let exact_flag = match exact {
Some(true) => true
_ => false
}
let state = self.state.val
let new_state : LocatorState = {
page: state.page,
selector: state.selector,
index: state.index,
has_text: Some(text),
has_text_exact: exact_flag,
has_not_text: state.has_not_text,
label_text: state.label_text,
label_text_exact: state.label_text_exact,
label_scope_selector: state.label_scope_selector,
}
{ state: { val: new_state } }
}
///|
/// Get by label within this locator
pub fn Locator::get_by_label(
self : Locator,
text : String,
exact? : Bool,
) -> Locator {
let exact_flag = match exact {
Some(true) => true
_ => false
}
let state = self.state.val
let selector = state.selector + " label"
let new_state : LocatorState = {
page: state.page,
selector,
index: None,
has_text: None,
has_text_exact: false,
has_not_text: state.has_not_text,
label_text: Some(text),
label_text_exact: exact_flag,
label_scope_selector: Some(state.selector),
}
{ state: { val: new_state } }
}
///|
/// Get by placeholder within this locator
pub fn Locator::get_by_placeholder(
self : Locator,
text : String,
exact? : Bool,
) -> Locator {
consume(exact)
self.locator("[placeholder=" + js_string_literal(text) + "]")
}
///|
/// Get by test ID within this locator
pub fn Locator::get_by_test_id(self : Locator, testId : String) -> Locator {
self.locator("[data-testid=" + js_string_literal(testId) + "]")
}
///| Action Methods
///|
/// Click element
pub async fn Locator::click(
self : Locator,
button? : String,
click_count? : Int,
delay? : Int,
force? : Bool,
modifiers? : Array[String],
no_wait_after? : Bool,
position? : (Double, Double),
timeout? : Int,
trial? : Bool,
) -> Unit {
consume(
(
button, click_count, delay, force, modifiers, no_wait_after, position, timeout,
trial,
),
)
let expr = self.element_expression() +
" && " +
self.element_expression() +
".click()"
let _ = self.page_eval(expr)
}
///|
/// Double click element
pub async fn Locator::dblclick(
self : Locator,
timeout? : Int,
force? : Bool,
) -> Unit {
consume((timeout, force))
let expr = "(() => { const el = " +
self.element_expression() +
"; if (!el) return; const ev = new MouseEvent('dblclick',{bubbles:true}); el.dispatchEvent(ev); })()"
let _ = self.page_eval(expr)
}
///|
/// Fill input
pub async fn Locator::fill(
self : Locator,
value : String,
force? : Bool,
no_wait_after? : Bool,
timeout? : Int,
) -> Unit {
consume((force, no_wait_after, timeout))
// Pass selector and value via callFunction arguments (avoids escaping issues)
let selector = self.state.val.selector
let page = self.state.val.page
let client = page.state.val.browser.state.val.client
let ctx = page.state.val.context_id
let args = [
json_to_remote_value(Json::string(selector)),
json_to_remote_value(Json::string(value)),
]
let result = client.script_call_function(
"(sel, val) => { const el = document.querySelector(sel); if (!el) return false; el.focus(); el.value = val; el.dispatchEvent(new InputEvent('input',{bubbles:true,data:val,inputType:'insertText'})); el.dispatchEvent(new Event('change',{bubbles:true})); return true; }",
ctx,
arguments=args,
)
consume(result)
}
///|
/// Clear input
pub async fn Locator::clear(
self : Locator,
force? : Bool,
no_wait_after? : Bool,
timeout? : Int,
) -> Unit {
consume((force, no_wait_after, timeout))
self.fill("")
}
///|
/// Type text (keystroke by keystroke)
pub async fn Locator::type_(
self : Locator,
text : String,
delay? : Int,
no_wait_after? : Bool,
timeout? : Int,
) -> Unit {
consume((delay, no_wait_after, timeout))
let expr = "(() => { const el = " +
self.element_expression() +
"; if (!el) return; el.value = (el.value || '') + " +
js_string_literal(text) +
"; el.dispatchEvent(new Event('input',{bubbles:true})); })()"
let _ = self.page_eval(expr)
}
///|
/// Press key
pub async fn Locator::press(
self : Locator,
key : String,
delay? : Int,
no_wait_after? : Bool,
timeout? : Int,
) -> Unit {
consume((delay, no_wait_after, timeout))
let expr = "(() => { const el = " +
self.element_expression() +
"; if (!el) return; const ev = new KeyboardEvent('keydown',{key:" +
js_string_literal(key) +
",bubbles:true}); el.dispatchEvent(ev); })()"
let _ = self.page_eval(expr)
}
///|
/// Check checkbox
pub async fn Locator::check(
self : Locator,
force? : Bool,
no_wait_after? : Bool,
timeout? : Int,
) -> Unit {
consume((force, no_wait_after, timeout))
let expr = "(() => { const el = " +
self.element_expression() +
"; if (!el) return; el.checked = true; el.dispatchEvent(new Event('change',{bubbles:true})); })()"
let _ = self.page_eval(expr)
}
///|
/// Uncheck checkbox
pub async fn Locator::uncheck(
self : Locator,
force? : Bool,
no_wait_after? : Bool,
timeout? : Int,
) -> Unit {
consume((force, no_wait_after, timeout))
let expr = "(() => { const el = " +
self.element_expression() +
"; if (!el) return; el.checked = false; el.dispatchEvent(new Event('change',{bubbles:true})); })()"
let _ = self.page_eval(expr)
}
///|
/// Set checkbox state
pub async fn Locator::set_checked(
self : Locator,
checked : Bool,
force? : Bool,
no_wait_after? : Bool,
timeout? : Int,
) -> Unit {
consume((force, no_wait_after, timeout))
if checked {
self.check()
} else {
self.uncheck()
}
}
///|
/// Select option(s) in dropdown
pub async fn Locator::select_option(
self : Locator,
values : Array[SelectOption],
force? : Bool,
no_wait_after? : Bool,
timeout? : Int,
) -> Array[String] {
consume((self, values, force, no_wait_after, timeout))
async_noop()
[]
}
///|
/// Hover over element
pub async fn Locator::hover(
self : Locator,
force? : Bool,
modifiers? : Array[String],
position? : (Double, Double),
timeout? : Int,
trial? : Bool,
) -> Unit {
consume((force, modifiers, position, timeout, trial))
let expr = "(() => { const el = " +
self.element_expression() +
"; if (!el) return; const ev = new MouseEvent('mouseover',{bubbles:true}); el.dispatchEvent(ev); })()"
let _ = self.page_eval(expr)
}
///|
/// Focus element
pub async fn Locator::focus(self : Locator, timeout? : Int) -> Unit {
consume(timeout)
let expr = self.element_expression() +
" && " +
self.element_expression() +
".focus()"
let _ = self.page_eval(expr)
}
///|
/// Blur element
pub async fn Locator::blur(self : Locator, timeout? : Int) -> Unit {
consume(timeout)
let expr = self.element_expression() +
" && " +
self.element_expression() +
".blur()"
let _ = self.page_eval(expr)
}
///|
/// Scroll element into view
pub async fn Locator::scroll_into_view_if_needed(
self : Locator,
timeout? : Int,
) -> Unit {
consume(timeout)
let expr = self.element_expression() +
" && " +
self.element_expression() +
".scrollIntoView()"
let _ = self.page_eval(expr)
}
///| Property Methods
///|
/// Get inner text
pub async fn Locator::inner_text(self : Locator, timeout? : Int) -> String {
consume(timeout)
match self.eval_string(self.element_expression() + "?.inner_text") {
Some(v) => v
None => ""
}
}
///|
/// Get inner HTML
pub async fn Locator::inner_html(self : Locator, timeout? : Int) -> String {
consume(timeout)
match self.eval_string(self.element_expression() + "?.inner_html") {
Some(v) => v
None => ""
}
}
///|
/// Get text content
pub async fn Locator::text_content(self : Locator, timeout? : Int) -> String? {
consume(timeout)
self.eval_string(self.element_expression() + "?.text_content")
}
///|
/// Get all text contents
pub async fn Locator::all_text_contents(self : Locator) -> Array[String] {
consume(self)
async_noop()
[]
}
///|
/// Get all inner texts
pub async fn Locator::all_inner_texts(self : Locator) -> Array[String] {
consume(self)
async_noop()
[]
}
///|
/// Get attribute
pub async fn Locator::get_attribute(
self : Locator,
name : String,
timeout? : Int,
) -> String? {
consume(timeout)
self.eval_string(
self.element_expression() +
"?.get_attribute(" +
js_string_literal(name) +
")",
)
}
///|
/// Get input value
pub async fn Locator::input_value(self : Locator, timeout? : Int) -> String {
consume(timeout)
match self.eval_string(self.element_expression() + "?.value") {
Some(v) => v
None => ""
}
}
///|
/// Check if element is visible
pub async fn Locator::is_visible(self : Locator, timeout? : Int) -> Bool {
consume(timeout)
match self.eval_bool(self.element_expression() + "?.offsetParent !== null") {
Some(v) => v
None => false
}
}
///|
/// Check if element is hidden
pub async fn Locator::is_hidden(self : Locator, timeout? : Int) -> Bool {
consume(timeout)
match self.eval_bool(self.element_expression() + "?.offsetParent === null") {
Some(v) => v
None => true
}
}
///|
/// Check if element is enabled
pub async fn Locator::is_enabled(self : Locator, timeout? : Int) -> Bool {
consume(timeout)
match self.eval_bool(self.element_expression() + "?.disabled === false") {
Some(v) => v
None => false
}
}
///|
/// Check if element is disabled
pub async fn Locator::is_disabled(self : Locator, timeout? : Int) -> Bool {
consume(timeout)
match self.eval_bool(self.element_expression() + "?.disabled === true") {
Some(v) => v
None => false
}
}
///|
/// Check if element is editable
pub async fn Locator::is_editable(self : Locator, timeout? : Int) -> Bool {
consume(timeout)
match self.eval_bool(self.element_expression() + "?.readOnly === false") {
Some(v) => v
None => false
}
}
///|
/// Check if checkbox is checked
pub async fn Locator::is_checked(self : Locator, timeout? : Int) -> Bool {
consume(timeout)
match self.eval_bool(self.element_expression() + "?.checked === true") {
Some(v) => v
None => false
}
}
///|
/// Get element count
pub async fn Locator::count(self : Locator) -> Int {
consume(self)
async_noop()
0
}
///|
/// Get bounding box
pub async fn Locator::bounding_box(
self : Locator,
timeout? : Int,
) -> BoundingBox? {
consume(timeout)
let expr = "(() => { const el = " +
self.element_expression() +
"; if (!el || !el.getBoundingClientRect) return null; const r = el.getBoundingClientRect(); return JSON.stringify({x:r.x,y:r.y,width:r.width,height:r.height}); })()"
match self.state.val.page.evaluate(expr) {
String(payload) =>
BoundingBox::from_json(@json.parse(payload) catch { _ => Json::null() })
_ => None
}
}
///| Wait Methods
///|
/// Wait for element to be visible/hidden/attached/detached
pub async fn Locator::wait_for(
self : Locator,
state? : String,
timeout? : Int,
) -> Unit {
let page = self.state.val.page
let selector = self.state.val.selector
match (state, timeout) {
(Some(s), Some(t)) => {
let _ = page.wait_for_selector(selector, state=s, timeout=t)
}
(Some(s), None) => {
let _ = page.wait_for_selector(selector, state=s)
}
(None, Some(t)) => {
let _ = page.wait_for_selector(selector, timeout=t)
}
(None, None) => {
let _ = page.wait_for_selector(selector)
}
}
}
///| Screenshot
///|
/// Take screenshot of element
pub async fn Locator::screenshot(
self : Locator,
path? : String,
type_? : String,
quality? : Int,
omit_background? : Bool,
timeout? : Int,
scale? : String,
) -> ResponseBody {
consume((self, path, type_, quality, omit_background, timeout, scale))
async_noop()
ResponseBody::bytes(b"")
}
///| Evaluate
///|
/// Evaluate JavaScript in element context
pub async fn Locator::evaluate(
self : Locator,
page_function : String,
arg? : Json,
timeout? : Int,
) -> Json {
consume(timeout)
match arg {
Some(value) => self.state.val.page.evaluate(page_function, arg=value)
None => self.state.val.page.evaluate(page_function)
}
}
///|
/// Evaluate all matching elements
pub async fn Locator::evaluate_all(
self : Locator,
page_function : String,
arg? : Json,
) -> Json {
match arg {
Some(value) => self.state.val.page.evaluate(page_function, arg=value)
None => self.state.val.page.evaluate(page_function)
}
}
///|
fn Locator::from_selector(page : Page, selector : String) -> Locator {
let state : LocatorState = {
page,
selector,
index: None,
has_text: None,
has_text_exact: false,
has_not_text: None,
label_text: None,
label_text_exact: false,
label_scope_selector: None,
}
{ state: { val: state } }
}
///|
fn Locator::element_expression(self : Locator) -> String {
let state = self.state.val
let selector = js_string_literal(state.selector)
let not_text_literal = match state.has_not_text {
Some(text) => js_string_literal(text)
None => "null"
}
match state.label_text {
Some(text) => {
let text_literal = js_string_literal(text)
let exact_flag = if state.label_text_exact { "true" } else { "false" }
let scope_literal = match state.label_scope_selector {
Some(value) => js_string_literal(value)
None => "null"
}
let index_expr = match state.index {
Some(i) =>
if i < 0 {
"return matches.length === 0 ? null : matches[matches.length - 1];"
} else {
"return matches.length <= " +
i.to_string() +
" ? null : matches[" +
i.to_string() +
"];"
}
None => "return matches.length === 0 ? null : matches[0];"
}
"(function(){ const labels = Array.from(document.querySelectorAll(" +
selector +
")); const txt = " +
text_literal +
"; const exact = " +
exact_flag +
"; const notTxt = " +
not_text_literal +
"; const scopeSelector = " +
scope_literal +
"; const scopes = scopeSelector ? Array.from(document.querySelectorAll(scopeSelector)) : []; const inScope = (el) => { if (!scopeSelector) return true; return scopes.some(s => s.contains(el)); }; const norm = (s) => (s || '').replace(/[\\u200B\\u200C\\u200D\\uFEFF]/g,'').replace(/[\\u00a0\\u1680\\u2000-\\u200A\\u202F\\u205F\\u3000]/g,' ').replace(/\\s+/g,' ').trim(); const query = norm(txt); const notQuery = notTxt ? norm(notTxt) : null; const textMatch = (t) => { const n = norm(t); return exact ? n === query : n.includes(query); }; const labelText = (el) => { const t = norm((el.inner_text || el.text_content || '')); return t.length > 0 ? t : null; }; const labelledByText = (el) => { const value = el.get_attribute && el.get_attribute('aria-labelledby'); if (!value) return null; const raw = value.split(/\\s+/).filter(Boolean); const ids = []; for (const id of raw) { if (ids.indexOf(id) >= 0) continue; ids.push(id); } const nodes = []; for (const id of ids) { const node = document.getElementById(id); if (node) nodes.push(node); } nodes.sort((a,b) => { if (a === b) return 0; const pos = a.compareDocumentPosition(b); if (pos & Node.DOCUMENT_POSITION_FOLLOWING) return -1; if (pos & Node.DOCUMENT_POSITION_PRECEDING) return 1; return 0; }); const parts = []; for (const node of nodes) { const lt = labelText(node); if (lt) parts.push(lt); } if (parts.length === 0) return null; return norm(parts.join(' ')); }; const ariaLabelText = (el) => { const value = el.get_attribute && el.get_attribute('aria-label'); if (!value) return null; const t = norm(value); return t.length > 0 ? t : null; }; const labelTextFor = (el) => { const parts = []; const id = el.get_attribute && el.get_attribute('id'); if (id) { const rel = Array.from(document.querySelectorAll('label[for=\"' + id + '\"]')); for (const label of rel) { const t = labelText(label); if (t) parts.push(t); } } if (parts.length > 0) return norm(parts.join(' ')); let parent = el.parentElement; while (parent) { if (parent.tagName === 'LABEL') { const t = labelText(parent); if (t) return t; break; } parent = parent.parentElement; } return null; }; const accessibleName = (el) => { const by = labelledByText(el); if (by) return by; const aria = ariaLabelText(el); if (aria) return aria; const lab = labelTextFor(el); if (lab) return lab; return null; }; const labelScore = (el) => { const by = el.get_attribute && el.get_attribute('aria-labelledby'); if (by && by.trim().length > 0) return 0; const id = el.get_attribute && el.get_attribute('id'); if (id) { const rel = document.querySelector('label[for=\"' + id + '\"]'); if (rel) return 1; } let parent = el.parentElement; while (parent) { if (parent.tagName === 'LABEL') return 2; parent = parent.parentElement; } const aria = el.get_attribute && el.get_attribute('aria-label'); if (aria && aria.trim().length > 0) return 3; return 4; }; const is_visible = (el) => { if (!el || el.nodeType !== 1) return false; let cur = el; while (cur) { if (cur.hidden) return false; const ariaHidden = cur.get_attribute && cur.get_attribute('aria-hidden'); if (ariaHidden === 'true') return false; const style = (cur.ownerDocument && cur.ownerDocument.defaultView) ? cur.ownerDocument.defaultView.getComputedStyle(cur) : null; if (style) { if (style.display === 'none' || style.visibility === 'hidden' || (style.visibility === 'collapse' && (cur.tagName === 'TABLE' || cur.tagName === 'TBODY' || cur.tagName === 'TR' || cur.tagName === 'TD' || cur.tagName === 'TH')) || style.contentVisibility === 'hidden') return false; const op = parseFloat(style.opacity || '1'); if (op == 0) return false; } cur = cur.parentElement; } if (el.getClientRects && el.getClientRects().length === 0) return false; if (el.getBoundingClientRect) { const r = el.getBoundingClientRect(); if (r && (r.width <= 0 || r.height <= 0)) return false; } return true; }; const nodeDepth = (n) => { let d = 0; let cur = n; while (cur) { d = d + 1; cur = cur.parentNode; } return d; }; const domDistance = (a, b) => { if (!a || !b) return 1e9; let da = nodeDepth(a); let db = nodeDepth(b); let pa = a; let pb = b; while (da > db) { pa = pa.parentNode; da = da - 1; } while (db > da) { pb = pb.parentNode; db = db - 1; } while (pa && pb && pa !== pb) { pa = pa.parentNode; pb = pb.parentNode; } if (!pa || !pb) return 1e9; const lca = pa; return (nodeDepth(a) - nodeDepth(lca)) + (nodeDepth(b) - nodeDepth(lca)); }; const labelNodesFor = (el) => { const nodes = []; const labelledBy = el.get_attribute && el.get_attribute('aria-labelledby'); if (labelledBy) { const raw = labelledBy.split(/\\s+/).filter(Boolean); const ids = []; for (const id of raw) { if (ids.indexOf(id) >= 0) continue; ids.push(id); } for (const id of ids) { const node = document.getElementById(id); if (node) nodes.push(node); } } const id = el.get_attribute && el.get_attribute('id'); if (id) { const rel = Array.from(document.querySelectorAll('label[for=\"' + id + '\"]')); for (const label of rel) nodes.push(label); } let parent = el.parentElement; while (parent) { if (parent.tagName === 'LABEL') { nodes.push(parent); break; } parent = parent.parentElement; } return nodes; }; const labelDistance = (el) => { const nodes = labelNodesFor(el); if (nodes.length === 0) return 1e9; let best = 1e9; for (const node of nodes) { if (node.tagName === 'LABEL') { const forId = node.get_attribute && node.get_attribute('for'); const id = el.get_attribute && el.get_attribute('id'); if (forId && id && forId === id) return 0; if (node.contains && node.contains(el)) return 1; } const d = domDistance(el, node); if (d < best) best = d; } return best; }; let matches = []; const candidates = Array.from(document.querySelectorAll('input, textarea, select, button, [aria-labelledby], [aria-label]')); for (const el of candidates) { if (!inScope(el)) continue; const name = accessibleName(el); if (!name) continue; if (notQuery && name.includes(notQuery)) continue; if (!textMatch(name)) continue; matches.push(el); } if (matches.length === 0) { for (const label of labels) { if (!inScope(label)) continue; const t = labelText(label); if (!t) continue; if (notQuery && t.includes(notQuery)) continue; if (!textMatch(t)) continue; let control = null; const forId = label.get_attribute && label.get_attribute('for'); if (forId) { const target = document.getElementById(forId); if (target) control = target; } if (!control && label.querySelector) { control = label.querySelector('input, textarea, select, button'); } if (!control) control = label; if (!inScope(control)) continue; matches.push(control); } } if (matches.length > 1) { const visible = matches.filter(is_visible); if (visible.length > 0) matches = visible; matches.sort((a, b) => { const da = labelDistance(a); const db = labelDistance(b); if (da < db) return -1; if (da > db) return 1; const sa = labelScore(a); const sb = labelScore(b); if (sa < sb) return -1; if (sa > sb) return 1; if (a === b) return 0; const pos = a.compareDocumentPosition(b); if (pos & Node.DOCUMENT_POSITION_FOLLOWING) return -1; if (pos & Node.DOCUMENT_POSITION_PRECEDING) return 1; return 0; }); } } const pick = (function(){ " +
index_expr +
" })(); return pick; })()"
}
None =>
match state.has_text {
Some(text) => {
let text_literal = js_string_literal(text)
let exact_flag = if state.has_text_exact { "true" } else { "false" }
let index_expr = match state.index {
Some(i) =>
if i < 0 {
"return matches.length === 0 ? null : matches[matches.length - 1];"
} else {
"return matches.length <= " +
i.to_string() +
" ? null : matches[" +
i.to_string() +
"];"
}
None => "return matches.length === 0 ? null : matches[0];"
}
"(function(){ const els = Array.from(document.querySelectorAll(" +
selector +
")); const txt = " +
text_literal +
"; const exact = " +
exact_flag +
"; const notTxt = " +
not_text_literal +
"; const matches = els.filter(el => { const t = (el.text_content || '').trim(); if (notTxt && t.includes(notTxt)) return false; return exact ? t === txt : t.includes(txt); }); " +
index_expr +
" })()"
}
None =>
match state.has_not_text {
Some(_) => {
let index_expr = match state.index {
Some(i) =>
if i < 0 {
"return matches.length === 0 ? null : matches[matches.length - 1];"
} else {
"return matches.length <= " +
i.to_string() +
" ? null : matches[" +
i.to_string() +
"];"
}
None => "return matches.length === 0 ? null : matches[0];"
}
"(function(){ const els = Array.from(document.querySelectorAll(" +
selector +
")); const notTxt = " +
not_text_literal +
"; const matches = els.filter(el => { const t = (el.text_content || '').trim(); if (notTxt && t.includes(notTxt)) return false; return true; }); " +
index_expr +
" })()"
}
None =>
match state.index {
Some(i) =>
if i < 0 {
"(function(){ const els = document.querySelectorAll(" +
selector +
"); return els.length === 0 ? null : els[els.length - 1]; })()"
} else {
"(document.querySelectorAll(" +
selector +
")[" +
i.to_string() +
"] || null)"
}
None => "document.querySelector(" + selector + ")"
}
}
}
}
}
///|
async fn Locator::page_eval(self : Locator, expression : String) -> Unit {
let page = self.state.val.page
let result = page.evaluate(expression)
consume(result)
}
///|
async fn Locator::eval_string(self : Locator, expression : String) -> String? {
let page_state = self.state.val.page.state.val
let result = page_state.browser.state.val.client.script_evaluate(
expression,
page_state.context_id,
)
match result {
Ok(json) =>
match json_get_object(json, "result") {
Some(value) => remote_value_to_string(value)
None => None
}
Err(_) => None
}
}
///|
async fn Locator::eval_bool(self : Locator, expression : String) -> Bool? {
let page_state = self.state.val.page.state.val
let result = page_state.browser.state.val.client.script_evaluate(
expression,
page_state.context_id,
)
match result {
Ok(json) =>
match json_get_object(json, "result") {
Some(value) =>
match remote_value_to_json(value) {
True => Some(true)
False => Some(false)
_ => None
}
None => None
}
Err(_) => None
}
}