|
import ast |
|
import os |
|
import pathlib |
|
import platform |
|
import re |
|
|
|
import bs4 |
|
import gymnasium as gym |
|
import pytest |
|
from pyparsing.exceptions import ParseException |
|
|
|
|
|
import browsergym.core |
|
from browsergym.core.action.highlevel import HighLevelActionSet |
|
from browsergym.core.action.parsers import NamedArgument, highlevel_action_parser |
|
from browsergym.core.constants import BROWSERGYM_ID_ATTRIBUTE as BID_ATTR |
|
from browsergym.utils.obs import flatten_dom_to_str |
|
|
|
_IS_MAC_OS = platform.system() == "Darwin" |
|
|
|
__SLOW_MO = 1000 if "DISPLAY_BROWSER" in os.environ else None |
|
__HEADLESS = False if "DISPLAY_BROWSER" in os.environ else True |
|
__TIMEOUT = 500 |
|
|
|
__DATA_DIR = pathlib.Path(__file__).resolve().parent / "data" |
|
|
|
TEXTBOX_URL = f"file://{__DATA_DIR}/textbox.html" |
|
EXAMPLE_URL = f"file://{__DATA_DIR}/example.html" |
|
HOVER_URL = f"file://{__DATA_DIR}/hover.html" |
|
INEXISTANT_FILE_URL = f"file://{__DATA_DIR}/no_file_here.html" |
|
LONG_PAGE_URL = f"file://{__DATA_DIR}/long_page.html" |
|
TEXT_INPUT_URL = f"file://{__DATA_DIR}/input_type/text_input.html" |
|
URL_INPUT_URL = f"file://{__DATA_DIR}/input_type/url_input.html" |
|
CHECKBOX_URL = f"file://{__DATA_DIR}/input_type/checkbox_input.html" |
|
MULTI_IFRAME_URL = f"file://{__DATA_DIR}/basic_iframe_site/basic_iframe_2.html" |
|
OBSTRUCTED_CHECKBOX_URL = f"file://{__DATA_DIR}/obstructed_checkbox_page.html" |
|
LOTS_OF_IFRAMES_URL = f"file://{__DATA_DIR}/lots_of_iframes.html" |
|
|
|
|
|
def test_action_parser(): |
|
parser = highlevel_action_parser |
|
|
|
with pytest.raises(ParseException): |
|
function_calls = parser.parse_string("", parseAll=True) |
|
assert not function_calls |
|
|
|
function_calls = parser.parse_string("a()", parseAll=True) |
|
assert len(function_calls) == 1 |
|
|
|
function_calls = parser.parse_string(" a ( ) \n\n\t", parseAll=True) |
|
assert len(function_calls) == 1 |
|
|
|
function_calls = parser.parse_string(" a ( ) b() \n \tc()", parseAll=True) |
|
assert [function_name for function_name, _ in function_calls] == ["a", "b", "c"] |
|
|
|
function_calls = parser.parse_string('a(12, 12.2, "text", (1, 2, 3), ["a", 23])', parseAll=True) |
|
_, function_args = function_calls[0] |
|
assert function_args == [12, 12.2, "text", (1, 2, 3), ["a", 23]] |
|
|
|
function_calls = parser.parse_string('a(x=12, y = 12.2, other = "text")', parseAll=True) |
|
_, function_args = function_calls[0] |
|
assert function_args == [ |
|
NamedArgument(name="x", value=12), |
|
NamedArgument(name="y", value=12.2), |
|
NamedArgument(name="other", value="text"), |
|
] |
|
|
|
function_calls = parser.parse_string('a(12, y = 12.2, other = "text")', parseAll=True) |
|
_, function_args = function_calls[0] |
|
assert function_args == [ |
|
12, |
|
NamedArgument(name="y", value=12.2), |
|
NamedArgument(name="other", value="text"), |
|
] |
|
|
|
with pytest.raises(ParseException): |
|
function_calls = parser.parse_string('a(x = 12, 12.2, other = "text")', parseAll=True) |
|
|
|
with pytest.raises(ParseException): |
|
function_calls = parser.parse_string('a(12, 12.2, 1 = "text")', parseAll=True) |
|
|
|
with pytest.raises(ParseException): |
|
function_calls = parser.parse_string("a(1-)", parseAll=True) |
|
|
|
with pytest.raises(ParseException): |
|
function_calls = parser.parse_string("a(1/2)", parseAll=True) |
|
|
|
function_calls = parser.parse_string('a("""\nsome\ntext\\"\\"""")', parseAll=True) |
|
_, function_args = function_calls[0] |
|
assert function_args == ['\nsome\ntext""'] |
|
|
|
function_calls = parser.parse_string("a('\"some\\ntext\"')", parseAll=True) |
|
_, function_args = function_calls[0] |
|
assert function_args == ['"some\ntext"'] |
|
|
|
function_calls = parser.parse_string('#comment\na("# not comment") #comment \n ', parseAll=True) |
|
assert len(function_calls) == 1 |
|
function_name, function_args = function_calls[0] |
|
assert function_name == "a" |
|
assert function_args == ["# not comment"] |
|
|
|
function_calls = parser.parse_string('fun(12, x="val", y={"aaa": 23})', parseAll=True) |
|
function_name, function_args = function_calls[0] |
|
assert function_name == "fun" |
|
assert function_args == [ |
|
12, |
|
NamedArgument(name="x", value="val"), |
|
NamedArgument(name="y", value={"aaa": 23}), |
|
] |
|
|
|
|
|
def test_valid_action(): |
|
action_set = HighLevelActionSet() |
|
|
|
env = gym.make( |
|
"browsergym/openended", |
|
task_kwargs={"start_url": CHECKBOX_URL}, |
|
headless=__HEADLESS, |
|
slow_mo=__SLOW_MO, |
|
timeout=__TIMEOUT, |
|
action_mapping=action_set.to_python_code, |
|
) |
|
|
|
def get_checkbox_elem(obs): |
|
soup = bs4.BeautifulSoup(flatten_dom_to_str(obs["dom_object"]), "lxml") |
|
checkbox = soup.find("input", attrs={"type": "checkbox", "id": "vehicle1"}) |
|
return checkbox |
|
|
|
obs, info = env.reset() |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
assert not checkbox.has_attr("checked") |
|
|
|
|
|
action = f"""\ |
|
click({repr(checkbox.get(BID_ATTR))}, "17" screen") # typo here |
|
""" |
|
with pytest.raises(ValueError): |
|
python_action = action_set.to_python_code(action) |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert "Received an empty action." in obs["last_action_error"] |
|
assert not checkbox.has_attr("checked") |
|
|
|
|
|
action = f"""\ |
|
click({repr(checkbox.get(BID_ATTR))}) |
|
""" |
|
python_action = action_set.to_python_code(action) |
|
|
|
assert python_action.count("\nclick(") == 1 |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
assert checkbox.has_attr("checked") |
|
|
|
|
|
action = f"""\ |
|
click({repr(checkbox.get(BID_ATTR))}) |
|
click({repr(checkbox.get(BID_ATTR))}) |
|
""" |
|
python_action = action_set.to_python_code(action) |
|
|
|
assert python_action.count("\nclick(") == 2 |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
assert checkbox.has_attr("checked") |
|
|
|
|
|
action = f"""\ |
|
click({repr(checkbox.get(BID_ATTR))}) |
|
click({repr(checkbox.get(BID_ATTR))}) |
|
click({repr(checkbox.get(BID_ATTR))}) |
|
""" |
|
python_action = action_set.to_python_code(action) |
|
|
|
assert python_action.count("\nclick(") == 3 |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
assert not checkbox.has_attr("checked") |
|
|
|
|
|
action = f"""\ |
|
click({repr(checkbox.get(BID_ATTR))}) click({repr(checkbox.get(BID_ATTR))}) click({repr(checkbox.get(BID_ATTR))}) |
|
""" |
|
python_action = action_set.to_python_code(action) |
|
|
|
assert python_action.count("\nclick(") == 3 |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
assert checkbox.has_attr("checked") |
|
|
|
|
|
action = f"""\ |
|
click( {repr(checkbox.get(BID_ATTR))} ) click({repr(checkbox.get(BID_ATTR))})\t |
|
noop() noop () noop( ) |
|
# THIS IS A COMMENT |
|
noop() # this is a noop() call |
|
click({repr(checkbox.get(BID_ATTR))}, ) |
|
#click({repr(checkbox.get(BID_ATTR))}) |
|
""" |
|
python_action = action_set.to_python_code(action) |
|
|
|
assert python_action.count("\nclick(") == 3 |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
assert not checkbox.has_attr("checked") |
|
|
|
|
|
action = f"""\ |
|
Below is code |
|
```python |
|
click( {repr(checkbox.get(BID_ATTR))} ) click({repr(checkbox.get(BID_ATTR))})\t |
|
noop() noop () noop( ) |
|
# THIS IS A COMMENT |
|
noop() # this is a noop() call |
|
click({repr(checkbox.get(BID_ATTR))}, ) |
|
#click({repr(checkbox.get(BID_ATTR))}) |
|
``` |
|
This is not code, just an explanation |
|
""" |
|
python_action = action_set.to_python_code(action) |
|
|
|
assert python_action.count("\nclick(") == 3 |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
assert checkbox.has_attr("checked") |
|
|
|
|
|
action = f"""\ |
|
Below is code |
|
```python |
|
noop() noop () noop( ) |
|
# THIS IS A COMMENT |
|
noop() # this is a noop() call |
|
click({repr(checkbox.get(BID_ATTR))}, ) |
|
#click({repr(checkbox.get(BID_ATTR))}) |
|
``` |
|
This is not code, just an explanation |
|
Below is more code |
|
```python |
|
click( {repr(checkbox.get(BID_ATTR))} ) click({repr(checkbox.get(BID_ATTR))})\t |
|
noop() noop () noop( ) |
|
# THIS IS A COMMENT |
|
noop() # this is a noop() call |
|
#click({repr(checkbox.get(BID_ATTR))}) |
|
``` |
|
This is not code, just an explanation |
|
""" |
|
python_action = action_set.to_python_code(action) |
|
|
|
assert python_action.count("\nclick(") == 3 |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
assert not checkbox.has_attr("checked") |
|
|
|
|
|
action = f"""\ |
|
Let's do a noop(), then noop () noop( ) then click({repr(checkbox.get(BID_ATTR))}, ) |
|
#click({repr(checkbox.get(BID_ATTR))}) |
|
Now let's do two more |
|
click( {repr(checkbox.get(BID_ATTR))} ) click({repr(checkbox.get(BID_ATTR))})\t |
|
noop() noop () noop( ) |
|
# THIS IS A COMMENT |
|
noop() # this is a noop() call |
|
#click({repr(checkbox.get(BID_ATTR))}) |
|
``` |
|
This is not code, just an explanation |
|
This is garbage |
|
""" |
|
python_action = action_set.to_python_code(action) |
|
|
|
assert python_action.count("\nclick(") == 3 |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
assert checkbox.has_attr("checked") |
|
|
|
env.close() |
|
|
|
|
|
def test_invalid_action(): |
|
action_set = HighLevelActionSet() |
|
|
|
env = gym.make( |
|
"browsergym/openended", |
|
task_kwargs={"start_url": CHECKBOX_URL}, |
|
headless=__HEADLESS, |
|
slow_mo=__SLOW_MO, |
|
timeout=__TIMEOUT, |
|
action_mapping=action_set.to_python_code, |
|
) |
|
obs, info = env.reset() |
|
|
|
|
|
action = f"""\ |
|
click("INVALID_BID") |
|
""" |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
|
|
|
|
assert "ValueError" in obs["last_action_error"] |
|
|
|
|
|
action = f"""\ |
|
click(None) |
|
""" |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
|
|
|
|
assert obs["last_action_error"] == "ValueError: expected a string, got None" |
|
|
|
|
|
action = f"""\ |
|
click(42.7) |
|
""" |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
|
|
|
|
assert obs["last_action_error"] == "ValueError: expected a string, got 42.7" |
|
|
|
|
|
action = f"""\ |
|
click([]) |
|
""" |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
|
|
|
|
assert obs["last_action_error"] == "ValueError: expected a string, got []" |
|
|
|
|
|
action = f"""\ |
|
click([42, "a", True, None]) |
|
""" |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
|
|
|
|
assert obs["last_action_error"] == "ValueError: expected a string, got [42, 'a', True, None]" |
|
|
|
|
|
action = f"""\ |
|
click({{}}) |
|
""" |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
|
|
|
|
assert obs["last_action_error"] == "ValueError: expected a string, got {}" |
|
|
|
|
|
action = f"""\ |
|
click({{"k": "aaa"}}) |
|
""" |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
|
|
|
|
assert obs["last_action_error"] == "ValueError: expected a string, got {'k': 'aaa'}" |
|
|
|
|
|
action = f"""\ |
|
click("4", "aa", "bb") |
|
""" |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
|
|
|
|
assert obs["last_action_error"] == "Error: Locator.click: modifiers: expected array, got string" |
|
|
|
|
|
action = f"""\ |
|
click() |
|
""" |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
|
|
|
|
assert ( |
|
obs["last_action_error"] |
|
== "TypeError: click() missing 1 required positional argument: 'bid'" |
|
) |
|
|
|
|
|
action = f"""\ |
|
click() |
|
""" |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
|
|
|
|
assert ( |
|
obs["last_action_error"] |
|
== "TypeError: click() missing 1 required positional argument: 'bid'" |
|
) |
|
|
|
|
|
with pytest.raises(NameError): |
|
action_set.to_python_code( |
|
f"""\ |
|
not_a_valid_action() |
|
""" |
|
) |
|
|
|
|
|
with pytest.raises(NameError): |
|
HighLevelActionSet(subsets=["coord"]).to_python_code( |
|
f"""\ |
|
fill("INVALID_BID", "some text") |
|
""" |
|
) |
|
|
|
|
|
with pytest.raises(ValueError): |
|
action_set.to_python_code( |
|
f"""\ |
|
import numpy as np |
|
""" |
|
) |
|
|
|
|
|
with pytest.raises(ValueError): |
|
action_set.to_python_code( |
|
f"""\ |
|
[ |
|
""" |
|
) |
|
|
|
|
|
with pytest.raises(ValueError): |
|
action_set.to_python_code( |
|
f"""\ |
|
click |
|
""" |
|
) |
|
|
|
env.close() |
|
|
|
|
|
def test_click_through_frames(): |
|
action_set = HighLevelActionSet() |
|
|
|
env = gym.make( |
|
"browsergym/openended", |
|
task_kwargs={"start_url": MULTI_IFRAME_URL}, |
|
headless=__HEADLESS, |
|
slow_mo=__SLOW_MO, |
|
timeout=__TIMEOUT, |
|
action_mapping=action_set.to_python_code, |
|
) |
|
|
|
obs, info = env.reset() |
|
|
|
soup = bs4.BeautifulSoup(flatten_dom_to_str(obs["dom_object"]), "lxml") |
|
checkbox = soup.find("input", attrs={"type": "checkbox", "id": "checkbox_2"}) |
|
|
|
|
|
assert checkbox.has_attr("checked") |
|
|
|
|
|
action = f"""\ |
|
click({repr(checkbox.get(BID_ATTR))}) |
|
""" |
|
python_action = action_set.to_python_code(action) |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
|
|
soup = bs4.BeautifulSoup(flatten_dom_to_str(obs["dom_object"]), "lxml") |
|
checkbox = soup.find("input", attrs={"type": "checkbox", "id": "checkbox_2"}) |
|
|
|
|
|
assert not checkbox.has_attr("checked") |
|
|
|
env.close() |
|
|
|
|
|
def test_fill_through_iframe(): |
|
action_set = HighLevelActionSet() |
|
|
|
env = gym.make( |
|
"browsergym/openended", |
|
task_kwargs={"start_url": MULTI_IFRAME_URL}, |
|
headless=__HEADLESS, |
|
slow_mo=__SLOW_MO, |
|
timeout=__TIMEOUT, |
|
action_mapping=action_set.to_python_code, |
|
) |
|
|
|
obs, info = env.reset() |
|
|
|
soup = bs4.BeautifulSoup(flatten_dom_to_str(obs["dom_object"]), "lxml") |
|
text_input = soup.find( |
|
"input", attrs={"type": "text", "placeholder": "Enter text here in iframe"} |
|
) |
|
|
|
|
|
assert text_input.get("value") == "" |
|
|
|
|
|
action = f"""\ |
|
fill({repr(text_input.get(BID_ATTR))}, "This is a test value.") |
|
""" |
|
python_action = action_set.to_python_code(action) |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
|
|
soup = bs4.BeautifulSoup(flatten_dom_to_str(obs["dom_object"]), "lxml") |
|
text_input = soup.find( |
|
"input", attrs={"type": "text", "placeholder": "Enter text here in iframe"} |
|
) |
|
|
|
|
|
assert text_input.get("value") == "This is a test value." |
|
|
|
env.close() |
|
|
|
|
|
def test_click(): |
|
action_set = HighLevelActionSet() |
|
|
|
env = gym.make( |
|
"browsergym/openended", |
|
task_kwargs={"start_url": CHECKBOX_URL}, |
|
headless=__HEADLESS, |
|
slow_mo=__SLOW_MO, |
|
timeout=__TIMEOUT, |
|
action_mapping=action_set.to_python_code, |
|
) |
|
|
|
def get_checkbox_elem(obs): |
|
soup = bs4.BeautifulSoup(flatten_dom_to_str(obs["dom_object"]), "lxml") |
|
checkbox = soup.find("input", attrs={"type": "checkbox", "id": "vehicle1"}) |
|
return checkbox |
|
|
|
obs, info = env.reset() |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not checkbox.has_attr("checked") |
|
|
|
|
|
action = f""" |
|
click({repr(checkbox.get(BID_ATTR))}) |
|
""" |
|
python_action = action_set.to_python_code(action) |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
|
|
|
|
assert checkbox.has_attr("checked") |
|
|
|
|
|
action = f"""\ |
|
click({repr(checkbox.get(BID_ATTR))}) |
|
""" |
|
python_action = action_set.to_python_code(action) |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
|
|
|
|
assert not checkbox.has_attr("checked") |
|
|
|
|
|
action = f"""\ |
|
click({repr(checkbox.get(BID_ATTR))}) |
|
click({repr(checkbox.get(BID_ATTR))}) |
|
""" |
|
python_action = action_set.to_python_code(action) |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
|
|
|
|
assert not checkbox.has_attr("checked") |
|
|
|
env.close() |
|
|
|
|
|
def test_hover(): |
|
action_set = HighLevelActionSet(subsets=["bid", "coord"]) |
|
|
|
env = gym.make( |
|
"browsergym/openended", |
|
task_kwargs={"start_url": HOVER_URL}, |
|
headless=__HEADLESS, |
|
slow_mo=__SLOW_MO, |
|
timeout=__TIMEOUT, |
|
action_mapping=action_set.to_python_code, |
|
) |
|
|
|
def get_button_elem(obs): |
|
soup = bs4.BeautifulSoup(flatten_dom_to_str(obs["dom_object"]), "lxml") |
|
button = soup.find("input", attrs={"type": "button"}) |
|
return button |
|
|
|
obs, info = env.reset() |
|
button = get_button_elem(obs) |
|
|
|
assert not obs["last_action_error"] |
|
assert button.get("value") == "Hover me" |
|
|
|
action = f""" |
|
hover({repr(button.get(BID_ATTR))}) |
|
""" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
button = get_button_elem(obs) |
|
|
|
assert not obs["last_action_error"] |
|
assert button.get("value") == "Hello world!" |
|
|
|
action = f""" |
|
mouse_move(0, 0) |
|
""" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
button = get_button_elem(obs) |
|
|
|
assert not obs["last_action_error"] |
|
assert button.get("value") == "Hover me" |
|
|
|
env.close() |
|
|
|
|
|
def test_fill_type_press(): |
|
action_set = HighLevelActionSet(subsets=["bid", "coord"]) |
|
env = gym.make( |
|
"browsergym/openended", |
|
task_kwargs={"start_url": TEXT_INPUT_URL}, |
|
headless=__HEADLESS, |
|
slow_mo=__SLOW_MO, |
|
timeout=__TIMEOUT, |
|
action_mapping=action_set.to_python_code, |
|
) |
|
|
|
def get_fname_lname_elems(obs): |
|
soup = bs4.BeautifulSoup(flatten_dom_to_str(obs["dom_object"]), "lxml") |
|
fname = soup.find("input", attrs={"id": "fname"}) |
|
lname = soup.find("input", attrs={"id": "lname"}) |
|
return fname, lname |
|
|
|
obs, info = env.reset() |
|
fname, lname = get_fname_lname_elems(obs) |
|
|
|
|
|
action = f""" |
|
fill({repr(fname.get(BID_ATTR))}, 'Christian') |
|
""" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
fname, lname = get_fname_lname_elems(obs) |
|
|
|
assert not obs["last_action_error"] |
|
assert fname.get("value") == "Christian" |
|
assert lname.get("value") == "" |
|
|
|
|
|
action = f""" |
|
fill({repr(lname.get(BID_ATTR))}, 'Clavier') |
|
""" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
fname, lname = get_fname_lname_elems(obs) |
|
|
|
assert not obs["last_action_error"] |
|
assert fname.get("value") == "Christian" |
|
assert lname.get("value") == "Clavier" |
|
|
|
|
|
action = f""" |
|
focus({repr(fname.get(BID_ATTR))}) keyboard_type('Gérard') |
|
""" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
fname, lname = get_fname_lname_elems(obs) |
|
|
|
assert not obs["last_action_error"] |
|
assert fname.get("value") == "ChristianGérard" |
|
assert lname.get("value") == "Clavier" |
|
|
|
|
|
action = f""" |
|
click({repr(lname.get(BID_ATTR))}) keyboard_insert_text('Jugnot') |
|
""" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
fname, lname = get_fname_lname_elems(obs) |
|
|
|
assert not obs["last_action_error"] |
|
assert fname.get("value") == "ChristianGérard" |
|
assert lname.get("value") == "ClavierJugnot" |
|
|
|
|
|
action = f""" |
|
clear({repr(lname.get(BID_ATTR))}) keyboard_insert_text('Jugnot') |
|
""" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
fname, lname = get_fname_lname_elems(obs) |
|
|
|
assert not obs["last_action_error"] |
|
assert fname.get("value") == "ChristianGérard" |
|
assert lname.get("value") == "Jugnot" |
|
|
|
|
|
action = f""" |
|
click({repr(fname.get(BID_ATTR))}) |
|
# clear the field |
|
keyboard_press('End') |
|
keyboard_down('Shift') |
|
keyboard_press('Home') |
|
keyboard_up('Shift') |
|
keyboard_press('Backspace') |
|
# insert text |
|
keyboard_insert_text('Gérard') |
|
""" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
fname, lname = get_fname_lname_elems(obs) |
|
|
|
assert not obs["last_action_error"] |
|
assert fname.get("value") == "Gérard" |
|
assert lname.get("value") == "Jugnot" |
|
|
|
|
|
action = f""" |
|
fill({repr(fname.get(BID_ATTR))}, '') |
|
""" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
fname, lname = get_fname_lname_elems(obs) |
|
|
|
assert not obs["last_action_error"] |
|
assert fname.get("value") == "" |
|
assert lname.get("value") == "Jugnot" |
|
|
|
|
|
action = f""" |
|
keyboard_type('Jean') |
|
""" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
fname, lname = get_fname_lname_elems(obs) |
|
|
|
assert not obs["last_action_error"] |
|
assert fname.get("value") == "Jean" |
|
assert lname.get("value") == "Jugnot" |
|
|
|
|
|
action = f""" |
|
mouse_click(0, 0) |
|
""" |
|
obs, reward, terminated, truncated, info = env.step(action) |
|
fname, lname = get_fname_lname_elems(obs) |
|
|
|
assert not obs["last_action_error"] |
|
assert fname.get("value") == "Jean" |
|
assert lname.get("value") == "Jugnot" |
|
|
|
action = f""" |
|
keyboard_type('Reno') |
|
""" |
|
obs, reward, terminated, truncated, info = env.step(action) |
|
fname, lname = get_fname_lname_elems(obs) |
|
|
|
assert not obs["last_action_error"] |
|
assert fname.get("value") == "Jean" |
|
assert lname.get("value") == "Jugnot" |
|
|
|
env.close() |
|
|
|
|
|
@pytest.mark.skip(reason="Not implemented yet") |
|
def test_dblclick(): |
|
pass |
|
|
|
|
|
|
|
def test_key_press(): |
|
action_set = HighLevelActionSet(subsets=["bid", "coord"]) |
|
|
|
env = gym.make( |
|
"browsergym/openended", |
|
task_kwargs={"start_url": TEXT_INPUT_URL}, |
|
headless=__HEADLESS, |
|
slow_mo=__SLOW_MO, |
|
timeout=__TIMEOUT, |
|
action_mapping=action_set.to_python_code, |
|
) |
|
|
|
obs, info = env.reset() |
|
|
|
def get_fname_lname_elems(obs): |
|
soup = bs4.BeautifulSoup(flatten_dom_to_str(obs["dom_object"]), "lxml") |
|
fname = soup.find("input", attrs={"id": "fname"}) |
|
lname = soup.find("input", attrs={"id": "lname"}) |
|
return fname, lname |
|
|
|
fname, lname = get_fname_lname_elems(obs) |
|
|
|
action = f""" |
|
fill({repr(fname.get(BID_ATTR))}, "Christian") |
|
keyboard_press({repr("Meta+a" if _IS_MAC_OS else "Control+a")}) |
|
keyboard_press({repr("Meta+c" if _IS_MAC_OS else "Control+c")}) |
|
click({repr(lname.get(BID_ATTR))}) |
|
keyboard_press({repr("Meta+v" if _IS_MAC_OS else "Control+v")}) |
|
""" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
|
|
assert not obs["last_action_error"] |
|
|
|
fname, lname = get_fname_lname_elems(obs) |
|
|
|
assert lname.get("value") == "Christian" |
|
|
|
env.close() |
|
|
|
|
|
def test_goto(): |
|
url1 = URL_INPUT_URL |
|
url2 = TEXT_INPUT_URL |
|
|
|
env = gym.make( |
|
"browsergym/openended", |
|
task_kwargs={"start_url": url1}, |
|
headless=__HEADLESS, |
|
slow_mo=__SLOW_MO, |
|
timeout=__TIMEOUT, |
|
) |
|
|
|
obs, info = env.reset() |
|
|
|
assert obs["url"] == url1 |
|
|
|
action = f""" |
|
goto({repr(url2)}) |
|
""" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
|
|
assert not obs["last_action_error"] |
|
|
|
assert obs["url"] == url2 |
|
|
|
action = """ |
|
go_back() |
|
""" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
|
|
assert not obs["last_action_error"] |
|
|
|
assert obs["url"] == url1 |
|
|
|
action = """ |
|
go_forward() |
|
""" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
|
|
assert not obs["last_action_error"] |
|
|
|
assert obs["url"] == url2 |
|
|
|
env.close() |
|
|
|
|
|
def test_scroll(): |
|
action_set = HighLevelActionSet(subsets=["coord"]) |
|
|
|
env = gym.make( |
|
"browsergym/openended", |
|
task_kwargs={"start_url": LONG_PAGE_URL}, |
|
headless=__HEADLESS, |
|
slow_mo=__SLOW_MO, |
|
timeout=__TIMEOUT, |
|
action_mapping=action_set.to_python_code, |
|
) |
|
|
|
def extract_coords_from_elem(elem): |
|
return ast.literal_eval(elem.get("center")) |
|
|
|
def get_top_bottom_elems(obs): |
|
soup = bs4.BeautifulSoup( |
|
flatten_dom_to_str( |
|
obs["dom_object"], obs["extra_element_properties"], with_center_coords=True |
|
), |
|
"lxml", |
|
) |
|
top = soup.find("input", attrs={"type": "checkbox", "id": "top"}) |
|
bottom = soup.find("input", attrs={"type": "checkbox", "id": "bottom"}) |
|
return top, bottom |
|
|
|
obs, info = env.reset() |
|
top, bottom = get_top_bottom_elems(obs) |
|
top_x, top_y = extract_coords_from_elem(top) |
|
bottom_x, bottom_y = extract_coords_from_elem(bottom) |
|
|
|
|
|
assert not top.has_attr("checked") |
|
|
|
assert not bottom.has_attr("checked") |
|
|
|
|
|
action = f"mouse_click({repr(top_x)}, {repr(top_y)})" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
|
|
top, bottom = get_top_bottom_elems(obs) |
|
top_x, top_y = extract_coords_from_elem(top) |
|
bottom_x, bottom_y = extract_coords_from_elem(bottom) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
|
|
assert top.has_attr("checked") |
|
|
|
assert not bottom.has_attr("checked") |
|
|
|
top, bottom = get_top_bottom_elems(obs) |
|
top_x, top_y = extract_coords_from_elem(top) |
|
bottom_x, bottom_y = extract_coords_from_elem(bottom) |
|
|
|
|
|
action = f"mouse_click({repr(bottom_x)}, {repr(bottom_y)})" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
|
|
top, bottom = get_top_bottom_elems(obs) |
|
top_x, top_y = extract_coords_from_elem(top) |
|
bottom_x, bottom_y = extract_coords_from_elem(bottom) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
|
|
assert top.has_attr("checked") |
|
|
|
assert not bottom.has_attr("checked") |
|
|
|
|
|
action = f"scroll(0, -500)" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
|
|
top, bottom = get_top_bottom_elems(obs) |
|
prev_top_x, prev_top_y = top_x, top_y |
|
top_x, top_y = extract_coords_from_elem(top) |
|
prev_bottom_x, prev_bottom_y = bottom_x, bottom_y |
|
bottom_x, bottom_y = extract_coords_from_elem(bottom) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
|
|
|
|
assert prev_top_x == top_x and prev_top_y == top_y |
|
assert prev_bottom_x == bottom_x and prev_bottom_y == bottom_y |
|
|
|
|
|
action = f"scroll(0, 500)" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
|
|
top, bottom = get_top_bottom_elems(obs) |
|
prev_top_x, prev_top_y = top_x, top_y |
|
top_x, top_y = extract_coords_from_elem(top) |
|
prev_bottom_x, prev_bottom_y = bottom_x, bottom_y |
|
bottom_x, bottom_y = extract_coords_from_elem(bottom) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
|
|
|
|
assert prev_top_x == top_x and prev_top_y > top_y |
|
assert prev_bottom_x == bottom_x and prev_bottom_y > bottom_y |
|
|
|
env.close() |
|
|
|
|
|
def test_tab_actions(): |
|
action_set = HighLevelActionSet(subsets=["tab", "nav"]) |
|
|
|
env = gym.make( |
|
"browsergym/openended", |
|
task_kwargs={"start_url": CHECKBOX_URL}, |
|
headless=__HEADLESS, |
|
slow_mo=__SLOW_MO, |
|
timeout=__TIMEOUT, |
|
action_mapping=action_set.to_python_code, |
|
) |
|
obs, info = env.reset() |
|
assert not obs["last_action_error"] |
|
assert len(obs["open_pages_urls"]) == 1 |
|
assert len(obs["open_pages_titles"]) == 1 |
|
assert obs["active_page_index"] == 0 |
|
assert obs["open_pages_urls"][obs["active_page_index"][0]] == obs["url"] |
|
|
|
obs, reward, terminated, truncated, info = env.step("new_tab()") |
|
assert not obs["last_action_error"] |
|
assert len(obs["open_pages_urls"]) == 2 |
|
assert len(obs["open_pages_titles"]) == 2 |
|
assert obs["active_page_index"] == 1 |
|
assert obs["open_pages_urls"][obs["active_page_index"][0]] == obs["url"] |
|
|
|
obs, reward, terminated, truncated, info = env.step(f"goto({repr(TEXTBOX_URL)})") |
|
assert not obs["last_action_error"] |
|
assert len(obs["open_pages_urls"]) == 2 |
|
assert len(obs["open_pages_titles"]) == 2 |
|
assert obs["active_page_index"] == 1 |
|
assert obs["open_pages_urls"][obs["active_page_index"][0]] == obs["url"] |
|
|
|
obs, reward, terminated, truncated, info = env.step("tab_focus(0)") |
|
assert not obs["last_action_error"] |
|
assert len(obs["open_pages_urls"]) == 2 |
|
assert len(obs["open_pages_titles"]) == 2 |
|
assert obs["active_page_index"] == 0 |
|
assert obs["open_pages_urls"][obs["active_page_index"][0]] == obs["url"] |
|
|
|
obs, reward, terminated, truncated, info = env.step("tab_close()") |
|
assert not obs["last_action_error"] |
|
assert len(obs["open_pages_urls"]) == 1 |
|
assert len(obs["open_pages_titles"]) == 1 |
|
assert obs["active_page_index"] == 0 |
|
assert obs["open_pages_urls"][obs["active_page_index"][0]] == obs["url"] |
|
|
|
env.close() |
|
|
|
|
|
def test_mouse_down_up(): |
|
action_set = HighLevelActionSet(subsets=["bid", "coord"]) |
|
|
|
env = gym.make( |
|
"browsergym/openended", |
|
task_kwargs={"start_url": CHECKBOX_URL}, |
|
headless=__HEADLESS, |
|
slow_mo=__SLOW_MO, |
|
timeout=__TIMEOUT, |
|
action_mapping=action_set.to_python_code, |
|
) |
|
|
|
def get_checkbox_elem(obs): |
|
soup = bs4.BeautifulSoup( |
|
flatten_dom_to_str( |
|
obs["dom_object"], obs["extra_element_properties"], with_center_coords=True |
|
), |
|
"lxml", |
|
) |
|
checkbox = soup.find("input", attrs={"type": "checkbox", "id": "vehicle1"}) |
|
return checkbox |
|
|
|
obs, info = env.reset() |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
assert not checkbox.has_attr("checked") |
|
|
|
|
|
x, y = ast.literal_eval(checkbox.get("center")) |
|
action = f"""\ |
|
mouse_click({repr(x)}, {repr(y)}) |
|
""" |
|
python_action = action_set.to_python_code(action) |
|
|
|
assert python_action.count("\nmouse_") == 1 |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
assert checkbox.has_attr("checked") |
|
|
|
|
|
x, y = ast.literal_eval(checkbox.get("center")) |
|
action = f"""\ |
|
mouse_move(0, 0) |
|
mouse_move({repr(x)}, {repr(y)}) |
|
mouse_down({repr(x)}, {repr(y)}) |
|
mouse_up({repr(x)}, {repr(y)}) |
|
""" |
|
python_action = action_set.to_python_code(action) |
|
|
|
assert python_action.count("\nmouse_") == 4 |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
assert not checkbox.has_attr("checked") |
|
|
|
|
|
x, y = ast.literal_eval(checkbox.get("center")) |
|
action = f"""\ |
|
mouse_move(0, 0) |
|
mouse_move({repr(x)}, {repr(y)}) |
|
mouse_down({repr(x)}, {repr(y)}, button="left") |
|
mouse_up({repr(x)}, {repr(y)}, "left") |
|
mouse_down({repr(x)}, {repr(y)}) |
|
mouse_up({repr(x)}, {repr(y)}) |
|
""" |
|
python_action = action_set.to_python_code(action) |
|
|
|
assert python_action.count("\nmouse_") == 6 |
|
|
|
obs, reward, term, trunc, info = env.step(action) |
|
checkbox = get_checkbox_elem(obs) |
|
|
|
|
|
assert not obs["last_action_error"] |
|
assert not checkbox.has_attr("checked") |
|
|
|
|
|
|
|
@pytest.mark.parametrize("retry_with_force", [True, False]) |
|
def test_forced_actions(retry_with_force): |
|
action_set = HighLevelActionSet(subsets=["bid"], retry_with_force=retry_with_force) |
|
env = gym.make( |
|
"browsergym/openended", |
|
task_kwargs={"start_url": OBSTRUCTED_CHECKBOX_URL}, |
|
headless=__HEADLESS, |
|
slow_mo=__SLOW_MO, |
|
timeout=__TIMEOUT, |
|
action_mapping=action_set.to_python_code, |
|
) |
|
|
|
obs, info = env.reset() |
|
|
|
def get_checkbox(obs): |
|
soup = bs4.BeautifulSoup(flatten_dom_to_str(obs["dom_object"]), "lxml") |
|
checkbox = soup.find("input", attrs={"id": "hobbies-checkbox-1"}) |
|
return checkbox |
|
|
|
checkbox = get_checkbox(obs) |
|
|
|
action = f""" |
|
click({repr(checkbox.get(BID_ATTR))}) |
|
""" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
checkbox = get_checkbox(obs) |
|
if retry_with_force: |
|
assert not obs["last_action_error"] |
|
assert checkbox.get("checked", False) == False |
|
else: |
|
assert obs["last_action_error"] |
|
assert checkbox.has_attr("checked") |
|
|
|
env.close() |
|
|
|
|
|
|
|
@pytest.mark.slow |
|
def test_iframe_bid(): |
|
action_set = HighLevelActionSet(subsets=["bid"]) |
|
env = gym.make( |
|
"browsergym/openended", |
|
task_kwargs={"start_url": LOTS_OF_IFRAMES_URL}, |
|
headless=__HEADLESS, |
|
slow_mo=__SLOW_MO, |
|
timeout=__TIMEOUT, |
|
action_mapping=action_set.to_python_code, |
|
) |
|
|
|
obs, info = env.reset() |
|
|
|
def get_checkbox(obs, i): |
|
soup = bs4.BeautifulSoup(flatten_dom_to_str(obs["dom_object"]), "lxml") |
|
checkbox = soup.find("input", attrs={"id": f"checkbox{i}"}) |
|
return checkbox |
|
|
|
|
|
checkboxes = [ |
|
(0, "a"), |
|
|
|
|
|
(29, "aD"), |
|
] |
|
for id, iframe_bid in checkboxes: |
|
|
|
|
|
checkbox = get_checkbox(obs, id) |
|
bid = checkbox.get(BID_ATTR) |
|
|
|
|
|
assert re.match(f"^{iframe_bid}[0-9]+$", bid) |
|
|
|
action = f""" |
|
click({repr(bid)}) |
|
""" |
|
|
|
obs, reward, terminated, truncated, info = env.step(action) |
|
assert not obs["last_action_error"] |
|
|
|
|
|
checkbox = get_checkbox(obs, id) |
|
assert checkbox.has_attr("checked") |
|
|
|
env.close() |
|
|