← Back to skills

Domain skill

jetblue

Markdown synced from browser-harness domain skills.

Host
jetblue
Files
1

Agent prompt

Use this skill

Copy this prompt into your coding agent to make it enable browser-harness domain skills and read this exact domain folder before automating.

Set up https://github.com/browser-use/browser-harness for me if it is not already installed. If setup is needed, read `install.md` first to install and connect it to my real browser. Then read `SKILL.md` for normal usage and always read `helpers.py` because that is where the browser-harness functions are.

Enable domain skills if they are not already enabled by setting `BH_DOMAIN_SKILLS=1` for browser-harness. Use the `jetblue` domain skill from `agent-workspace/domain-skills/jetblue/`. Read every markdown file for this domain before inventing an approach:
- agent-workspace/domain-skills/jetblue/checkout.md

Use those domain-skill notes to complete my task for `jetblue` in my real browser. When you open a setup, verification, or task tab, activate it so I can see the active browser tab.

Skill contents

What the agent will read

jetblue / checkout

checkout.md

Source
  • Stop before booking or payment submission unless the user authorizes that specific purchase.
  • End-to-end JetBlue (jetblue.com) one-way cash checkout: search -> flight -> cart -> traveler details -> seats/extras -> payment. Verified reachable to payment-entry with test data in 9 minutes.
  • Home: https://www.jetblue.com
  • Search results (deep link, skips home form): https://www.jetblue.com/booking/flights?from={IATA}&to={IATA}&depart=YYYY-MM-DD&isMultiCity=false&noOfRoute=1&adults=1&children=0&infants=0&roundTripFaresFlag=false&sharedMar...
Show full markdown

Stop before booking or payment submission unless the user authorizes that specific purchase.

End-to-end JetBlue (jetblue.com) one-way cash checkout: search -> flight -> cart -> traveler details -> seats/extras -> payment. Verified reachable to payment-entry with test data in ~9 minutes.

Site map

  • Home: https://www.jetblue.com
  • Search results (deep link, skips home form): https://www.jetblue.com/booking/flights?from={IATA}&to={IATA}&depart=YYYY-MM-DD&isMultiCity=false&noOfRoute=1&adults=1&children=0&infants=0&roundTripFaresFlag=false&sharedMarket=false&usePoints=false
  • Cart: https://www.jetblue.com/booking/cart
  • Checkout (all three steps share this URL; tabs rendered as sections): https://www.jetblue.com/booking/checkout (titles change: Traveler Details -> Seats & Extras -> Review & Pay)

Deep-linking the search URL is much faster than interacting with the home-page search form (which uses React aria-generated ids and a custom dot-city-selector autocomplete).

Framework

Angular + custom web components (prefix jb-*, dot-*, cb-*). No shadow DOM — everything is in the light DOM, which means standard querySelector + coord clicks work. Common components:

  • jb-select / jb-select-button — dropdowns. Open via coord click; options render as <span> inside a jb-flyout. No role=option set; match by innerText equality.
  • jb-autocomplete — text autocomplete (e.g. city). Input id jb-autocomplete-N-search.
  • jb-input / jb-input-label — text fields. Input id jb-input-label-id-N-input. Label text lives in a sibling <label>.
  • jb-radio — radio buttons (payment method selection).
  • jb-date-picker — date dropdown.
  • dot-booker-air-form / dot-traveler-selector — larger form blocks.

Flow & stable selectors

1. Search results page

Flight cards are <jb-card> elements containing text like "Core\nOptions from\n$94". Click one of these to expand fare options. Selector: Array.from(document.querySelectorAll('jb-card')).find(c => c.innerText.includes('Core')) then click its center.

After expansion, three <button> elements appear with text exactly "Select". Click one.

2. Fare upsell modal

A modal appears with two buttons: "Select Blue Basic" and "Select Blue". Click by innerText equality. Picking Blue Basic takes you straight to /booking/cart.

3. Cart page

Single button: "Continue to checkout". Click it -> /booking/checkout (Traveler Details).

4. Traveler Details

First task: click the button with exact text "Continue as guest" (alternative is Sign in). This reveals the guest form below.

Form fields (use document.getElementById):

  • #jb-input-label-id-3-input -> First Name
  • #jb-input-label-id-4-input -> Middle (optional)
  • #jb-input-label-id-5-input -> Last Name
  • #jb-input-label-id-6-input (type=email) -> Email
  • #radar-address-autocomplete -> Address line 1 (powered by Radar; pick a suggestion from the autocomplete list — it auto-fills city/state/ZIP)
  • #trip-contact-address-line-2 -> Address line 2
  • #trip-contact-city / #trip-contact-postal-code
  • input[type=tel] -> Phone (there's only one on the page)

Dropdowns (use jb-select matched by first-line innerText):

  • Title (required; options: Mr/Mrs/Miss/Ms/Dr)
  • Gender (options: Female/Male)
  • Month / Day / Year (DOB) — Month options are short (Jan, Feb, ...); Year list is long, scroll the list via el.scrollIntoView({block:'center'}) on the target span.

N-index on jb-input-label-id-N shifts as sections render; do not hard-code it without verifying label text. First Name may be id-3 on one render, id-8 on another.

Submit: button with text "Next: Seats & Extras".

5. Seats & Extras

Fastest path: click the element with exact text "Skip to Review & Pay" (it's a <div> that acts as a shortcut link). Skips seat selection entirely.

6. Review & Pay (payment)

Payment method default: radio #jb-radio-0 = "Credit or debit card" (already selected).

Visible fields in the parent frame:

  • input[aria-label="Name as it appears on card"]
  • input[aria-label="Expiration date in mm/yy format"] (accepts raw 1229 input)

Card number and CVV are inside cross-origin TokenEx iframes (this is the critical trap — see below).

Critical trap: TokenEx OOPIF iframes for PAN/CVV

Iframes:

  • #tx_iframe_tokenex-card-number-container (src: htp.tokenex.com/iframe/v3?...&Mode=Data)
  • #tx_iframe_cvv_tokenex-security-code-container (src: ...&Mode=CVV)

These are out-of-process iframes (OOPIF). Compositor-level coordinate clicks focus the right pixel, but Input.dispatchKeyEvent fired against the top-level page session is not routed into the OOPIF — the iframe never sees the keystrokes. Both type_text() and page-session CDP key events silently fail, leaving the fields empty. The visible error after submit attempts: "Card number is required" despite the field being focused.

What works: attach to each OOPIF's CDP target and dispatch key events against the iframe's own session.

Use card_number from the authorized task input. Never log its value. For the CVV, repeat the frame lookup and session cleanup with #tx_iframe_cvv_tokenex-security-code-container. Verify its TokenEx host before typing.

python
from urllib.parse import urlsplit

if urlsplit(page_info()["url"]).hostname != "www.jetblue.com":
    raise RuntimeError("Select the authorized JetBlue checkout tab first")
pan_url = js("document.querySelector('#tx_iframe_tokenex-card-number-container')?.src ?? null")
if not pan_url or urlsplit(pan_url).hostname != "htp.tokenex.com":
    raise RuntimeError("The expected TokenEx card frame is not ready")
targets = cdp("Target.getTargets")["targetInfos"]
matching = [t for t in targets if t["type"] == "iframe" and t.get("url") == pan_url]
if len(matching) != 1:
    raise RuntimeError("The checkout card frame does not identify exactly one target")
pan_id = matching[0]["targetId"]

# The payment frame needs its own session for keyboard events.
pan_session = cdp("Target.attachToTarget", targetId=pan_id, flatten=True)['sessionId']
try:
    cdp("Runtime.evaluate", expression='document.getElementById("data").focus();', session_id=pan_session)
    for ch in card_number:
        cdp("Input.dispatchKeyEvent", type="keyDown", text=ch, key=ch, code=f"Digit{ch}", session_id=pan_session)
        cdp("Input.dispatchKeyEvent", type="keyUp", key=ch, code=f"Digit{ch}", session_id=pan_session)
finally:
    cdp("Target.detachFromTarget", sessionId=pan_session)

This is the general pattern for any TokenEx-hosted PCI field (Delta, United, and other airlines/hotels also use TokenEx). Direct .value = inside the iframe won't trigger TokenEx's internal validation; key events do.

Billing address auto-populates from the Traveler Details step — no re-entry.

Waits

  • wait_for_load() after each major navigation is fine.
  • After clicking a fare card, wait ~3s for the fare-options sub-cards to render.
  • After clicking Select Blue Basic, the URL transitions (3-5s).
  • After Next: Seats & Extras and Skip to Review & Pay, wait ~5s — TokenEx iframes take a second to inject after the page renders; querying #tx_iframe_tokenex-card-number-container too early returns null.
  • DOB Year dropdown has ~100 entries; use element.scrollIntoView({block:'center'}) on the target <span> inside the open flyout — page window.scrollBy does not scroll the flyout's internal list reliably.

Did-not-work list

  • .value = "..." on the TokenEx iframe's #data input — no visible effect; TokenEx listens for keystrokes.
  • type_text() after a coord click into the PAN iframe — clicks hit the compositor but key events go to the top-level page, not the OOPIF.
  • cdp("Input.dispatchKeyEvent", ...) without a session_id — same reason; events land on the top frame.
  • Opening the DOB Year dropdown and clicking span[text=1990] before scrolling it into view — span was present in DOM but off-screen; click landed on a different year span.
  • querySelector('[role=option]') inside jb-flyout — options don't carry role=option; match by innerText on <span> children.

Antibot posture

No CAPTCHA, no Akamai/PerimeterX interstitial, no login wall, no 3DS/OTP on filled non-submitted fields. TrustArc cookie banner appears but is non-blocking — click Accept once or leave it (it covers the bottom-left corner and doesn't intercept form clicks).