diff --git a/Dockers/puppeteer-api/app/services/browser.py b/Dockers/puppeteer-api/app/services/browser.py index 2440732..9c50998 100644 --- a/Dockers/puppeteer-api/app/services/browser.py +++ b/Dockers/puppeteer-api/app/services/browser.py @@ -307,6 +307,27 @@ async def interactions_service( if not action: raise ValueError("Interaction must contain an action") + async def wait_for_xpath(xp: str, timeout_ms: int = 30000) -> None: + # Accept both raw XPath ("//div") and Playwright-style ("xpath=//div"). + normalized = xp[len("xpath=") :] if xp.startswith("xpath=") else xp + await page.wait_for_function( + """(xpath) => { + try { + return !!document.evaluate( + xpath, + document, + null, + XPathResult.FIRST_ORDERED_NODE_TYPE, + null + ).singleNodeValue; + } catch (e) { + return false; + } + }""", + normalized, + timeout=timeout_ms, + ) + # Prefer selector-based interactions when selectors are provided (CSS first, then XPath). if selector or xpath_selector: if action == "click": @@ -322,7 +343,9 @@ async def interactions_service( selector, ) else: - await page.wait_for_selector(f"xpath={xpath_selector}", timeout=30000) + if not xpath_selector: + raise ValueError("xpath_selector must be a non-empty string") + await wait_for_xpath(xpath_selector, timeout_ms=30000) await page.evaluate( """(xpath) => { const el = document.evaluate( @@ -335,7 +358,7 @@ async def interactions_service( if (!el) throw new Error(`No element for xpath: ${xpath}`); el.click(); }""", - xpath_selector, + xpath_selector[len("xpath=") :] if xpath_selector.startswith("xpath=") else xpath_selector, ) elif action == "type": @@ -358,7 +381,10 @@ async def interactions_service( {"sel": selector, "value": text}, ) else: - await page.wait_for_selector(f"xpath={xpath_selector}", timeout=30000) + if not xpath_selector: + raise ValueError("xpath_selector must be a non-empty string") + await wait_for_xpath(xpath_selector, timeout_ms=30000) + normalized_xpath = xpath_selector[len("xpath=") :] if xpath_selector.startswith("xpath=") else xpath_selector await page.evaluate( """({ xpath, value }) => { const el = document.evaluate( @@ -376,7 +402,7 @@ async def interactions_service( el.dispatchEvent(new Event('change', { bubbles: true })); } }""", - {"xpath": xpath_selector, "value": text}, + {"xpath": normalized_xpath, "value": text}, ) else: