mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
133 lines
6.7 KiB
Python
133 lines
6.7 KiB
Python
"""A submit we never located must prove the payload rendered before it counts as delivered.
|
|
|
|
Measured live on LinkedIn's feed composer 2026-07-28: both structured resolvers missed, so the tail
|
|
fell back to clicking a button literally named "Send". LinkedIn's feed composer submits with "Post",
|
|
so that click landed on some OTHER widget's Send, returned no error, and the composer cleared
|
|
anyway. Receipt passed. `sent_receipt=True`. Nothing was posted, on either the posts or the
|
|
comments tab.
|
|
|
|
That is the dangerous direction: a cleared composer cannot tell "submitted" from "dismissed", and
|
|
every honesty guarantee downstream is built on that receipt. So the guessed path, and only the
|
|
guessed path, has to show the text actually rendered on the page. The two resolved paths keep their
|
|
measured speed and are untouched.
|
|
"""
|
|
import pytest
|
|
|
|
from backend.apps.agents.browser import browser_send_script as ss
|
|
|
|
PAYLOAD = "hello from my automation"
|
|
COMMITTED = f'[2]<textbox "Write a message" value="{PAYLOAD}">'
|
|
CLEARED = '[2]<textbox "Write a message">\n[9]<button "Attach">'
|
|
|
|
|
|
def make_exec(*, submit_listed: bool, container_ok: bool, visible_after: bool, cleared: bool = True,
|
|
container_disabled: bool = False):
|
|
"""A composer whose submit is or isn't resolvable, and a page that does or doesn't end up
|
|
showing the payload. Records which route the tail took."""
|
|
calls = {"clicks": [], "lists": 0, "evals": []}
|
|
|
|
async def execute(tool, params, bid, tid):
|
|
if tool == "BrowserListInteractives":
|
|
calls["lists"] += 1
|
|
first = calls["lists"] == 1
|
|
state = COMMITTED if (first or not cleared) else CLEARED
|
|
if submit_listed:
|
|
state += '\n[14]<button "Send">'
|
|
return {"text": state}
|
|
if tool == "BrowserEvaluate":
|
|
expr = str(params.get("expression") or "")
|
|
calls["evals"].append(expr)
|
|
if "visible" in expr: # the delivery probe
|
|
return {"text": f'{{"visible": {str(visible_after).lower()}}}'}
|
|
if container_ok: # the container submit resolver
|
|
return {"text": '{"ok": true, "xPct": 50.0, "yPct": 50.0, "name": "Post"}'}
|
|
if container_disabled: # found it, the site is refusing it
|
|
return {"text": '{"ok": false, "disabled": true, "name": "post", '
|
|
'"why": "the submit control is present but DISABLED"}'}
|
|
return {"text": '{"ok": false, "why": "no submit control in the composer container"}'}
|
|
calls["clicks"].append((tool, params))
|
|
return {"ok": True}
|
|
return execute, calls
|
|
|
|
|
|
def send_index(state, composer_index):
|
|
for line in (state or "").splitlines():
|
|
if '<button "Send">' in line:
|
|
return (14, "Send")
|
|
return None
|
|
|
|
|
|
async def run_tail(**kw):
|
|
ex, calls = make_exec(**kw)
|
|
r = await ss.complete_send(PAYLOAD, COMMITTED, "b1", "t1", ex, send_index,
|
|
composer_index=2, current_url="https://www.linkedin.com/feed/")
|
|
return r, calls
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_a_guessed_send_that_never_rendered_is_not_delivered():
|
|
"""The exact live failure. Composer cleared, so the old code called it sent; the payload is
|
|
nowhere on the page, so it wasn't."""
|
|
r, calls = await run_tail(submit_listed=False, container_ok=False, visible_after=False)
|
|
assert any(t == "BrowserClickByName" for t, _ in calls["clicks"]), "this must take the guessed path"
|
|
assert r["delivered"] is False, "a guessed click with no rendered payload must not claim delivery"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_a_guessed_send_that_did_render_is_delivered():
|
|
"""The guard must not punish the guessed path when it genuinely worked, or the by-name fallback
|
|
(load-bearing for LinkedIn message threads, where the button really is 'Send') becomes useless."""
|
|
r, calls = await run_tail(submit_listed=False, container_ok=False, visible_after=True)
|
|
assert any(t == "BrowserClickByName" for t, _ in calls["clicks"])
|
|
assert r["delivered"] is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_a_resolved_submit_is_not_slowed_down_by_the_new_probe():
|
|
"""Speed guard. The ranked-index path is the proven one (X, ~23s end to end); it must not start
|
|
paying for a delivery probe it never needed."""
|
|
r, calls = await run_tail(submit_listed=True, container_ok=True, visible_after=False)
|
|
assert r["sent"] is True
|
|
assert r["delivered"] is None, "a resolved submit keeps trusting the receipt, as measured"
|
|
assert not any("visible" in e for e in calls["evals"]), "no delivery probe on the resolved path"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_the_container_resolved_submit_also_skips_the_probe():
|
|
"""Container resolution locates the real control inside the composer, so it is not a guess."""
|
|
r, calls = await run_tail(submit_listed=False, container_ok=True, visible_after=False)
|
|
assert r["sent"] is True
|
|
assert r["delivered"] is None
|
|
assert not any("visible" in e for e in calls["evals"])
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_a_composer_that_never_cleared_is_still_not_sent():
|
|
"""The probe is an EXTRA hurdle for guessed clicks, never a way around the receipt: if the
|
|
composer still holds the text, nothing was sent no matter what else is on the page."""
|
|
r, _ = await run_tail(submit_listed=False, container_ok=False, visible_after=True, cleared=False)
|
|
assert r["sent"] is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_a_disabled_submit_clicks_absolutely_nothing():
|
|
"""Issue #94, the reddit shape. Its submit is present and greyed out until the title is filled.
|
|
The old code read that as "no submit control", guessed at a button named Send, and the cleared
|
|
composer then read as a delivery for a post that never existed. A control the SITE is refusing
|
|
cannot be won by clicking harder, so the only correct move is to touch nothing."""
|
|
r, calls = await run_tail(submit_listed=False, container_ok=False, container_disabled=True,
|
|
visible_after=False)
|
|
assert calls["clicks"] == [], "a disabled submit must not produce ANY click, guessed or otherwise"
|
|
assert r["clicked"] is False and r["sent"] is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_a_disabled_submit_says_what_is_actually_wrong():
|
|
"""The note is the whole value of the fix: it is the one failure here a model can repair, by
|
|
filling whatever the form still wants. It has to name that, and it must not imply a post."""
|
|
r, _ = await run_tail(submit_listed=False, container_ok=False, container_disabled=True,
|
|
visible_after=False)
|
|
note = str(r["note"]).lower()
|
|
assert "disabled" in note and "nothing was posted" in note
|
|
assert "title" in note, "name the usual culprit so the model knows where to look"
|