This commit is contained in:
@@ -19,6 +19,7 @@ router = APIRouter(tags=["Browser"])
|
|||||||
class ClickInteraction(BaseModel):
|
class ClickInteraction(BaseModel):
|
||||||
action: Literal["click"]
|
action: Literal["click"]
|
||||||
selector: Optional[str] = None
|
selector: Optional[str] = None
|
||||||
|
xpath_selector: Optional[str] = None
|
||||||
x: Optional[int] = None
|
x: Optional[int] = None
|
||||||
y: Optional[int] = None
|
y: Optional[int] = None
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ class ClickInteraction(BaseModel):
|
|||||||
class TypeInteraction(BaseModel):
|
class TypeInteraction(BaseModel):
|
||||||
action: Literal["type"]
|
action: Literal["type"]
|
||||||
selector: Optional[str] = None
|
selector: Optional[str] = None
|
||||||
|
xpath_selector: Optional[str] = None
|
||||||
text: str
|
text: str
|
||||||
x: Optional[int] = None
|
x: Optional[int] = None
|
||||||
y: Optional[int] = None
|
y: Optional[int] = None
|
||||||
|
|||||||
@@ -289,10 +289,12 @@ async def interactions_service(
|
|||||||
if isinstance(interaction, dict):
|
if isinstance(interaction, dict):
|
||||||
action = interaction.get("action")
|
action = interaction.get("action")
|
||||||
selector = interaction.get("selector")
|
selector = interaction.get("selector")
|
||||||
|
xpath_selector = interaction.get("xpath_selector")
|
||||||
text = interaction.get("text")
|
text = interaction.get("text")
|
||||||
else:
|
else:
|
||||||
action = getattr(interaction, "action", None)
|
action = getattr(interaction, "action", None)
|
||||||
selector = getattr(interaction, "selector", None)
|
selector = getattr(interaction, "selector", None)
|
||||||
|
xpath_selector = getattr(interaction, "xpath_selector", None)
|
||||||
text = getattr(interaction, "text", None)
|
text = getattr(interaction, "text", None)
|
||||||
# Coordinates may be present on interaction models.
|
# Coordinates may be present on interaction models.
|
||||||
x = getattr(interaction, "x", None)
|
x = getattr(interaction, "x", None)
|
||||||
@@ -305,38 +307,77 @@ async def interactions_service(
|
|||||||
if not action:
|
if not action:
|
||||||
raise ValueError("Interaction must contain an action")
|
raise ValueError("Interaction must contain an action")
|
||||||
|
|
||||||
# Prefer selector-based interactions when selector is provided.
|
# Prefer selector-based interactions when selectors are provided (CSS first, then XPath).
|
||||||
if selector:
|
if selector or xpath_selector:
|
||||||
if action == "click":
|
if action == "click":
|
||||||
await page.wait_for_selector(selector, timeout=30000)
|
if selector:
|
||||||
# Avoid Playwright auto-scrolling: click via DOM.
|
await page.wait_for_selector(selector, timeout=30000)
|
||||||
await page.evaluate(
|
# Avoid Playwright auto-scrolling: click via DOM.
|
||||||
"""(sel) => {
|
await page.evaluate(
|
||||||
const el = document.querySelector(sel);
|
"""(sel) => {
|
||||||
if (!el) throw new Error(`No element for selector: ${sel}`);
|
const el = document.querySelector(sel);
|
||||||
el.click();
|
if (!el) throw new Error(`No element for selector: ${sel}`);
|
||||||
}""",
|
el.click();
|
||||||
selector,
|
}""",
|
||||||
)
|
selector,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await page.wait_for_selector(f"xpath={xpath_selector}", timeout=30000)
|
||||||
|
await page.evaluate(
|
||||||
|
"""(xpath) => {
|
||||||
|
const el = document.evaluate(
|
||||||
|
xpath,
|
||||||
|
document,
|
||||||
|
null,
|
||||||
|
XPathResult.FIRST_ORDERED_NODE_TYPE,
|
||||||
|
null
|
||||||
|
).singleNodeValue;
|
||||||
|
if (!el) throw new Error(`No element for xpath: ${xpath}`);
|
||||||
|
el.click();
|
||||||
|
}""",
|
||||||
|
xpath_selector,
|
||||||
|
)
|
||||||
|
|
||||||
elif action == "type":
|
elif action == "type":
|
||||||
if text is None:
|
if text is None:
|
||||||
raise ValueError("Type interaction must contain text")
|
raise ValueError("Type interaction must contain text")
|
||||||
await page.wait_for_selector(selector, timeout=30000)
|
if selector:
|
||||||
# Avoid Playwright auto-scrolling: focus/fill via DOM.
|
await page.wait_for_selector(selector, timeout=30000)
|
||||||
await page.evaluate(
|
# Avoid Playwright auto-scrolling: focus/fill via DOM.
|
||||||
"""({ sel, value }) => {
|
await page.evaluate(
|
||||||
const el = document.querySelector(sel);
|
"""({ sel, value }) => {
|
||||||
if (!el) throw new Error(`No element for selector: ${sel}`);
|
const el = document.querySelector(sel);
|
||||||
el.focus?.();
|
if (!el) throw new Error(`No element for selector: ${sel}`);
|
||||||
if ('value' in el) {
|
el.focus?.();
|
||||||
el.value = value;
|
if ('value' in el) {
|
||||||
el.dispatchEvent(new Event('input', { bubbles: true }));
|
el.value = value;
|
||||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
}
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
}""",
|
}
|
||||||
{"sel": selector, "value": text},
|
}""",
|
||||||
)
|
{"sel": selector, "value": text},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await page.wait_for_selector(f"xpath={xpath_selector}", timeout=30000)
|
||||||
|
await page.evaluate(
|
||||||
|
"""({ xpath, value }) => {
|
||||||
|
const el = document.evaluate(
|
||||||
|
xpath,
|
||||||
|
document,
|
||||||
|
null,
|
||||||
|
XPathResult.FIRST_ORDERED_NODE_TYPE,
|
||||||
|
null
|
||||||
|
).singleNodeValue;
|
||||||
|
if (!el) throw new Error(`No element for xpath: ${xpath}`);
|
||||||
|
el.focus?.();
|
||||||
|
if ('value' in el) {
|
||||||
|
el.value = value;
|
||||||
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
}""",
|
||||||
|
{"xpath": xpath_selector, "value": text},
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unknown interaction action: {action}")
|
raise ValueError(f"Unknown interaction action: {action}")
|
||||||
@@ -383,7 +424,7 @@ async def interactions_service(
|
|||||||
raise ValueError(f"Unknown interaction action: {action}")
|
raise ValueError(f"Unknown interaction action: {action}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("Interaction must contain either selector or x/y coordinates")
|
raise ValueError("Interaction must contain selector, xpath_selector, or x/y coordinates")
|
||||||
|
|
||||||
# Let the UI settle after each action.
|
# Let the UI settle after each action.
|
||||||
await page.wait_for_timeout(1000)
|
await page.wait_for_timeout(1000)
|
||||||
|
|||||||
Reference in New Issue
Block a user