--- id: "e1ac52dd-0667-4efa-91e9-42c589f4851f" name: "SCORM Iframe Debugging" micro_summary: "Read-only Compass/Docebo SCORM debugging recipe: reuse the existing tab, find the visible launcher iframe, attach to its CDP iframe target, inspect `iframe#sco`, and rediscover controls by accessibility labels plus geometry instead of fixed IDs/coords." status: "resolved" see_also: [] scope: "Read-only browser-harness skill for locating Compass/Docebo SCORM lesson iframes and discovering visible controls/elements without clicking through or completing the lesson." warnings: "Read-only debugging skill: do not click controls, call DOM `.click()`, or call SCORM completion APIs (`LMSSetValue`, `LMSCommit`, `LMSFinish`) unless the user explicitly changes the task from inspection to interaction. Recompute frame rects and CDP targets each run; target IDs and generated Storyline/React IDs are ephemeral." --- ## Purpose Use this when debugging a Compass/Docebo SCORM lesson page and the user only wants iframe/element discovery. ## Minimal reproducible discovery ```python # 1) Reuse the existing Compass tab. tabs = list_tabs() tab = next(t for t in tabs if "compass.billiontoone.com" in t["url"]) switch_tab(tab) wait_for_load() # 2) Parent-page iframe scan. print(js(r""" JSON.stringify([...document.querySelectorAll("iframe")].map((f, i) => { const r = f.getBoundingClientRect() return {i, title:f.title, id:f.id, name:f.name, src:f.src, rect:{x:r.x,y:r.y,w:r.width,h:r.height}} }), null, 2) """)) # 3) Attach to the launcher iframe target by URL substring. targets = cdp("Target.getTargets")["targetInfos"] launcher = next(t for t in targets if "scormapi_v60/launcher" in t["url"]) print(launcher["targetId"]) # 4) Inspect the inner SCO frame from the launcher context. print(js(r""" (() => { const f = document.querySelector("#sco") || [...document.querySelectorAll("iframe")] .find(x => x.getBoundingClientRect().width > 500 && x.getBoundingClientRect().height > 300) const doc = f.contentDocument return JSON.stringify({ scoSrc: f.src, title: doc.title, text: doc.body.innerText.slice(0, 1000) }, null, 2) })() """, target_id=launcher["targetId"])) ``` ## Element/control discovery inside SCO Prefer semantic/accessibility clues, then geometry. Storyline controls often expose `data-acc-text`, `aria-label`, generated `data-model-id`, `.cursor-hover`, and SVG/image assets. ```python print(js(r""" (() => { const doc = document.querySelector("#sco").contentDocument return JSON.stringify([...doc.querySelectorAll( "[data-acc-text], [aria-label], .cursor-hover, button, a, input, [role=button]" )].map(el => { const r = el.getBoundingClientRect() return { tag: el.tagName, id: el.id, modelId: el.getAttribute("data-model-id"), acc: el.getAttribute("data-acc-text") || el.getAttribute("aria-label"), text: (el.innerText || el.textContent || "").trim(), className: String(el.className), rect: {x:r.x,y:r.y,w:r.width,h:r.height,cx:r.x+r.width/2,cy:r.y+r.height/2} } }).filter(x => x.rect.w && x.rect.h), null, 2) })() """, target_id=launcher["targetId"])) ``` ## Coordinate conversion Parent-page click/debug coordinates are inner SCO coordinates plus the outer lesson iframe rect. For the observed Compass layout, the outer iframe had `{x:0,y:78,w:1728,h:885}`, so an inner control center `{x:1490,y:823}` mapped to page `{x:1490,y:901}`. Recompute this offset every run: ```python outer = json.loads(js(r""" (() => { const f = [...document.querySelectorAll("iframe")] .find(x => (x.title||"").includes("Lesson Content") || (x.src||"").includes("scormapi_v60/launcher")) const r = f.getBoundingClientRect() return JSON.stringify({x:r.x,y:r.y,w:r.width,h:r.height,src:f.src,title:f.title}) })() """)) ``` ## If pages/elements change Fallback order: 1. Find the visible parent iframe by `title` containing `Lesson Content` or `src` containing `scormapi_v60/launcher`. 2. Find the CDP iframe target whose URL contains `scormapi_v60/launcher`. 3. Inside launcher, use `iframe#sco`; if missing, choose the first large visible iframe. 4. Discover controls by `data-acc-text`, `aria-label`, visible text, and image asset names. 5. If labels are weak, use `.cursor-hover` plus geometry (bottom nav cluster, top-right arrow, etc.). 6. Verify any proposed coordinate with `doc.elementFromPoint(x, y)` before interaction. ## Observed example (target IDs are ephemeral) - Compass page target: `C9532B70107CA72C49B25DCF92F6E884` - Launcher iframe target: `9C57D3A34ED02A613BA2B1BFF14E63AF` - Parent iframe: title `Lesson Content iFrame`, rect `{x:0,y:78,w:1728,h:885}` - Inner SCO: `iframe#sco`, title `EmergencyPreparedness_10_English_090123_SCORM12` - Controls found by labels/geometry: previous (`noun_previous_920443.png`), play (`noun_play_920445.png`), help (`noun_help_2094879.png`), feedback (`noun_feedback_17698.png`), and top arrow text `→`.