Build the injected `ctx` automation-helper object shared by `run-script` and `adapter`. The returned snippet declares `const ctx = {...}` and is meant to be embedded at the top of an async IIFE, before user code runs. Both call sites reuse it so the helper surface stays in lockstep.
(args_str: &str)
| 91 | /// at the top of an async IIFE, before user code runs. Both call sites reuse it |
| 92 | /// so the helper surface stays in lockstep. |
| 93 | fn build_ctx_object(args_str: &str) -> String { |
| 94 | format!( |
| 95 | r#"const ctx = {{ |
| 96 | args: {args_str}, |
| 97 | wait: async (ms) => new Promise(r => setTimeout(r, ms)), |
| 98 | waitForText: async (text, timeout = 30000) => {{ |
| 99 | const start = Date.now(); |
| 100 | while (true) {{ |
| 101 | if (document.body && document.body.innerText.includes(text)) return; |
| 102 | if (timeout > 0 && Date.now() - start >= timeout) break; |
| 103 | await new Promise(r => setTimeout(r, {POLL_INTERVAL_MS})); |
| 104 | }} |
| 105 | throw new Error("Timeout waiting for text: " + text); |
| 106 | }}, |
| 107 | waitForSelector: async (selector, timeout = 30000) => {{ |
| 108 | const start = Date.now(); |
| 109 | while (true) {{ |
| 110 | if (document.querySelector(selector)) return; |
| 111 | if (timeout > 0 && Date.now() - start >= timeout) break; |
| 112 | await new Promise(r => setTimeout(r, {POLL_INTERVAL_MS})); |
| 113 | }} |
| 114 | throw new Error("Timeout waiting for selector: " + selector); |
| 115 | }}, |
| 116 | click: async (selector) => {{ |
| 117 | const el = document.querySelector(selector); |
| 118 | if (!el) throw new Error("Element not found: " + selector); |
| 119 | el.click(); |
| 120 | }}, |
| 121 | fill: async (selector, value) => {{ |
| 122 | const el = document.querySelector(selector); |
| 123 | if (!el) throw new Error("Element not found: " + selector); |
| 124 | // Look up the native property setter from the element's own |
| 125 | // prototype chain (rather than assigning directly) so that |
| 126 | // React/Vue-style frameworks, which override the setter to |
| 127 | // track state, still observe the update. |
| 128 | const setNativeProp = (element, prop, val) => {{ |
| 129 | let setter; |
| 130 | let proto = Object.getPrototypeOf(element); |
| 131 | while (proto) {{ |
| 132 | const desc = Object.getOwnPropertyDescriptor(proto, prop); |
| 133 | if (desc) {{ |
| 134 | setter = desc.set; |
| 135 | break; |
| 136 | }} |
| 137 | proto = Object.getPrototypeOf(proto); |
| 138 | }} |
| 139 | if (setter) {{ |
| 140 | setter.call(element, val); |
| 141 | }} else {{ |
| 142 | element[prop] = val; |
| 143 | }} |
| 144 | }}; |
| 145 | if (el.type === 'checkbox' || el.type === 'radio') {{ |
| 146 | // Checkboxes/radios toggle via `checked`, not `value`. A |
| 147 | // boolean (or "true"/"false") sets the state directly; any |
| 148 | // other value selects the input whose `value` it matches. |
| 149 | let checkedVal; |
| 150 | if (value === true || value === false) {{ |
no outgoing calls