Add frame-aware locate for cross-origin iframe steps
Selectors were only resolved against the top document, so steps targeting the Lucid WooCommerce checkout (rendered in a lucidtrading.com iframe under the dash.lucidtrading.com app) never matched — e.g. input#coupon_code_field. Add an optional `frame` on click/type/waitFor naming the <iframe> element. The extension measures the iframe in the top frame (pageFrameOffset), finds the element across child frames (pageLocate `relative` mode), and composes the two into desktop coordinates; the Python side is unchanged since the result keeps the same shape. Without `frame`, every path behaves as before. Point the Lucid checkout steps at the frame and harden the coupon selectors (the exact field id is still unconfirmed against the live iframe). Extension 0.13.0, runner 0.19.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
016f121f40
commit
66c99c5499
+9
-3
@@ -117,6 +117,7 @@ def perform(
|
||||
url: str = "",
|
||||
open_url: str = "",
|
||||
signed_out: str = "",
|
||||
frame: str = "",
|
||||
text: str | None = None,
|
||||
clear: bool = False,
|
||||
scale: float | None = None,
|
||||
@@ -141,7 +142,12 @@ def perform(
|
||||
rng = rng or random.Random()
|
||||
say = report or (lambda _m: None)
|
||||
|
||||
found = dash.locate(selector, index, url, timeout, open_url)
|
||||
# `frame` names the <iframe> the element lives in; the extension resolves the
|
||||
# selector inside it and offsets the coordinates back. Absent for the common
|
||||
# top-document case, and then locate behaves exactly as before.
|
||||
frame_opts = {"frame": frame} if frame else None
|
||||
|
||||
found = dash.locate(selector, index, url, timeout, open_url, options=frame_opts)
|
||||
|
||||
# Before any check that assumes we are on the real page.
|
||||
check_signed_in(found, signed_out)
|
||||
@@ -153,7 +159,7 @@ def perform(
|
||||
deadline = time.time() + COVERED_SETTLE_SECONDS
|
||||
while found.get("covered") and time.time() < deadline:
|
||||
time.sleep(COVERED_POLL_SECONDS)
|
||||
found = dash.locate(selector, index, url, timeout, open_url)
|
||||
found = dash.locate(selector, index, url, timeout, open_url, options=frame_opts)
|
||||
check_signed_in(found, signed_out)
|
||||
if not found.get("covered"):
|
||||
say("it cleared")
|
||||
@@ -224,7 +230,7 @@ def perform(
|
||||
|
||||
# Typing into a field that never took focus, or that ignores the input, fails
|
||||
# silently and is otherwise indistinguishable from success.
|
||||
after = dash.locate(selector, index, url, timeout, open_url)
|
||||
after = dash.locate(selector, index, url, timeout, open_url, options=frame_opts)
|
||||
check_signed_in(after, signed_out)
|
||||
got = after.get("value")
|
||||
if got is None:
|
||||
|
||||
+11
-2
@@ -34,7 +34,7 @@ HEARTBEAT_SECONDS = 2.0
|
||||
# Bumped whenever the step vocabulary or the locate protocol changes. Reported in
|
||||
# the heartbeat so the dashboard can say "restart your runner" instead of letting
|
||||
# a stale process fail on a step type it has never heard of.
|
||||
VERSION = "0.18.0"
|
||||
VERSION = "0.19.0"
|
||||
|
||||
# Shared with the heartbeat thread: whether a run is currently executing.
|
||||
_busy = threading.Event()
|
||||
@@ -97,11 +97,19 @@ def run_one_step(dash: Dashboard, step: dict, opts, rng, report, run_id: int | N
|
||||
if run_id is not None and run_status(dash, run_id) == "cancelled":
|
||||
raise actions.StepError("cancelled while waiting")
|
||||
|
||||
# A frame-scoped gate resolves the selector inside the named <iframe>;
|
||||
# `hidden` adds the visibility probe. Either can be set independently.
|
||||
loc_opts: dict = {}
|
||||
if by_sight:
|
||||
loc_opts["op"] = "visible"
|
||||
if step.get("frame"):
|
||||
loc_opts["frame"] = step["frame"]
|
||||
|
||||
try:
|
||||
seen = dash.locate(selector, int(step.get("index", 0)),
|
||||
step.get("urlPattern", "") or opts.url,
|
||||
opts.timeout, step.get("openUrl", "") or "",
|
||||
options={"op": "visible"} if by_sight else None)
|
||||
options=loc_opts or None)
|
||||
if by_sight:
|
||||
# An extension too old for the probe quietly does an ordinary
|
||||
# locate instead, whose result has no `visible` key — reading
|
||||
@@ -190,6 +198,7 @@ def run_one_step(dash: Dashboard, step: dict, opts, rng, report, run_id: int | N
|
||||
url=step.get("urlPattern", "") or opts.url,
|
||||
open_url=step.get("openUrl", "") or "",
|
||||
signed_out=step.get("signedOut", "") or "",
|
||||
frame=step.get("frame", "") or "",
|
||||
text=step.get("text"),
|
||||
clear=bool(step.get("clear")),
|
||||
scale=opts.scale,
|
||||
|
||||
Reference in New Issue
Block a user