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
- 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 ajb-flyout. Norole=optionset; match byinnerTextequality.jb-autocomplete— text autocomplete (e.g. city). Input idjb-autocomplete-N-search.jb-input/jb-input-label— text fields. Input idjb-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-codeinput[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) —Monthoptions are short (Jan,Feb, ...);Yearlist is long, scroll the list viael.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 raw1229input)
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.
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 & ExtrasandSkip to Review & Pay, wait ~5s — TokenEx iframes take a second to inject after the page renders; querying#tx_iframe_tokenex-card-number-containertoo early returns null. - DOB
Yeardropdown has ~100 entries; useelement.scrollIntoView({block:'center'})on the target<span>inside the open flyout — pagewindow.scrollBydoes not scroll the flyout's internal list reliably.
Did-not-work list
.value = "..."on the TokenEx iframe's#datainput — 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 asession_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]')insidejb-flyout— options don't carryrole=option; match byinnerTexton<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).