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:
Brandon Li
2026-09-02 13:22:10 -05:00
co-authored by Claude Opus 4.8
parent 016f121f40
commit 66c99c5499
5 changed files with 197 additions and 18 deletions
+21 -8
View File
@@ -15,8 +15,14 @@ export type AutomationStep =
// failing the run — for things that only sometimes appear, like a cookie
// banner or a confirmation modal. Only absence is tolerated: an element that
// is present but covered or off-screen still fails.
| { action: 'click'; selector: string; index?: number; skipIfNotFound?: boolean; urlPattern?: string; label?: string }
| { action: 'type'; selector: string; text: string; index?: number; clear?: boolean; skipIfNotFound?: boolean; urlPattern?: string; label?: string }
// `frame` targets an element inside a cross-origin <iframe> (e.g. the
// WooCommerce checkout modal). Its value is a selector for the <iframe>
// element in the top document; the step's own `selector` is then resolved
// inside that frame and its coordinates offset back to the page. Without it a
// selector only ever matches the top document. Needs extension 0.13.0+ and
// runner 0.19.0+.
| { action: 'click'; selector: string; index?: number; skipIfNotFound?: boolean; urlPattern?: string; frame?: string; label?: string }
| { action: 'type'; selector: string; text: string; index?: number; clear?: boolean; skipIfNotFound?: boolean; urlPattern?: string; frame?: string; label?: string }
| { action: 'wait'; seconds: number; label?: string }
// Point the firm's tab at a page. Omit `url` to use the firm's own. Skipped
// when the tab is already there, so it doesn't reload and lose page state.
@@ -32,7 +38,7 @@ export type AutomationStep =
// content-visibility, or collapsed to zero size all end the wait. It says
// nothing about being scrolled off-screen or covered by an overlay: those
// are still on the page. Needs extension 0.12.0+ and runner 0.18.0+.
| { action: 'waitFor'; selector: string; index?: number; absent?: boolean; hidden?: boolean; timeoutSeconds?: number; label?: string }
| { action: 'waitFor'; selector: string; index?: number; absent?: boolean; hidden?: boolean; timeoutSeconds?: number; frame?: string; label?: string }
// Scroll until the page stops adding elements matching `selector`, for lists
// that load progressively. `containerSelector` names the scrolling element
// when the automatic guess is wrong — these lists usually scroll inside a div
@@ -271,11 +277,18 @@ export const FIRMS: Firm[] = [
{ action: 'click', selector: '.slide:nth-child(2) .cta-btn', label: 'Open' },
{ action: 'click', selector: '.platform-btn:first-child', label: 'CQG' },
{ action: 'click', selector: 'input#coupon_code_field', label: 'Click on coupon code'},
{ action: 'type', selector: 'input#coupon_code_field', text: 'THEPROPFARM'},
{ action: 'click', selector: 'input#coupon_code_field + button'},
{ action: 'click', selector: '.validate-required:not(.woocommerce-validated)', label: 'Agree to terms' },
{ action: 'click', selector: '.validate-required:not(.woocommerce-validated)', label: 'Agree to terms 2' },
// The checkout renders in a WooCommerce iframe on
// lucidtrading.com (parent app is dash.lucidtrading.com), so
// every step here targets the frame. The coupon field id
// varies between WooCommerce's default (#coupon_code /
// name="coupon_code") and this template's custom id; list
// both so one of them matches. Apply is WooCommerce's
// named submit button, not a positional sibling.
{ action: 'click', selector: 'input#coupon_code, input[name="coupon_code"], input#coupon_code_field', frame: '#woocommerceModal iframe', label: 'Focus coupon field' },
{ action: 'type', selector: 'input#coupon_code, input[name="coupon_code"], input#coupon_code_field', frame: '#woocommerceModal iframe', text: 'THEPROPFARM', label: 'Enter coupon' },
{ action: 'click', selector: 'button[name="apply_coupon"], input[name="apply_coupon"]', frame: '#woocommerceModal iframe', label: 'Apply coupon' },
{ action: 'click', selector: '.validate-required:not(.woocommerce-validated)', frame: '#woocommerceModal iframe', label: 'Agree to terms' },
{ action: 'click', selector: '.validate-required:not(.woocommerce-validated)', frame: '#woocommerceModal iframe', label: 'Agree to terms 2' },
{ action: 'waitFor', selector: '#woocommerceModal', absent: true, hidden: true, timeoutSeconds: 30, label: 'Wait for the modal to close' },