kyle8581's picture
upload
dd39c08
import os
import pathlib
from time import time
import bs4
import gymnasium as gym
import pytest
# register openended gym environments
import browsergym.core
import browsergym.core.action
from browsergym.core.action.highlevel import HighLevelActionSet
from browsergym.core.action.python import PythonActionSet
from browsergym.core.constants import BROWSERGYM_ID_ATTRIBUTE as BID_ATTR
from browsergym.utils.obs import flatten_dom_to_str
__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"
TEST_PAGE = f"file://{__DATA_DIR}/test_page.html"
BASIC_IFRAME_PAGE = f"file://{__DATA_DIR}/basic_iframe_site/basic_iframe_2.html"
def test_gym_env():
action_set = PythonActionSet()
env = gym.make(
"browsergym/openended",
task_kwargs={"start_url": TEST_PAGE},
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"]
obs, reward, term, trunc, info = env.step(
f"""\
page.get_by_label("Name:").click()
page.get_by_label("Name:").fill("Janice")
page.get_by_label("Name:").press("Tab")
page.get_by_label("Email:").fill("[email protected]")
page.get_by_label("Email:").press("Tab")
page.get_by_label("Age:", exact=True).fill("21")
page.get_by_label("Age:", exact=True).press("Tab")
"""
)
assert obs["last_action_error"] == ""
assert reward == 0
assert term == False
assert trunc == False
obs, reward, term, trunc, info = env.step(
f"""\
page.get_by_label("Message:").fill("Hello")
page.get_by_label("Message:").press("Tab")
page.get_by_label("Subscribe to newsletter").check()
page.get_by_label("Subscribe to newsletter").press("Tab")
page.get_by_role("button", name="Submit").press("Enter")
"""
)
assert obs["last_action_error"] == ""
assert reward == 0
assert term == False
assert trunc == False
obs, reward, term, trunc, info = env.step(
f"""\
page.get_by_label("LABEL DOES NOT EXIST:").fill("Hello")
page.get_by_role("button", name="Submit").press("Enter")
"""
)
assert obs["last_action_error"] != ""
assert reward == 0
assert term == False
assert trunc == False
env.close()
def test_max_episode_steps():
# no max_steps
env = gym.make(
"browsergym/openended",
task_kwargs={"start_url": TEST_PAGE},
headless=__HEADLESS,
slow_mo=__SLOW_MO,
timeout=__TIMEOUT,
)
obs, info = env.reset()
obs, reward, term, trunc, info = env.step("")
assert term == False
assert trunc == False
obs, reward, term, trunc, info = env.step("")
assert term == False
assert trunc == False
# max_steps = 2
env = gym.make(
"browsergym/openended",
task_kwargs={"start_url": TEST_PAGE},
headless=__HEADLESS,
slow_mo=__SLOW_MO,
timeout=__TIMEOUT,
max_episode_steps=2,
)
obs, info = env.reset()
obs, reward, term, trunc, info = env.step("")
assert term == False
assert trunc == False
obs, reward, term, trunc, info = env.step("")
assert term == False
assert trunc == True
env.close()
def test_active_page():
action_set = PythonActionSet()
env = gym.make(
"browsergym/openended",
task_kwargs={"start_url": TEST_PAGE},
headless=__HEADLESS,
slow_mo=__SLOW_MO,
timeout=__TIMEOUT,
action_mapping=action_set.to_python_code,
)
obs, info = env.reset()
assert len(obs["open_pages_urls"]) == 1
assert obs["active_page_index"] == 0
obs, reward, term, trunc, info = env.step("page.context.new_page()")
assert len(obs["open_pages_urls"]) == 2
assert obs["active_page_index"] == 1
obs, reward, term, trunc, info = env.step("page.context.pages[0].mouse.click(5, 5)")
assert len(obs["open_pages_urls"]) == 2
assert obs["active_page_index"] == 0
obs, reward, term, trunc, info = env.step("page.context.pages[1].mouse.click(5, 5)")
assert len(obs["open_pages_urls"]) == 2
assert obs["active_page_index"] == 1
obs, reward, term, trunc, info = env.step("page.context.pages[1].close()")
assert len(obs["open_pages_urls"]) == 1
assert obs["active_page_index"] == 0
obs, reward, term, trunc, info = env.step("page.close()")
assert len(obs["open_pages_urls"]) == 1
assert obs["active_page_index"] == 0
obs, reward, term, trunc, info = env.step("page.context.new_page()")
assert len(obs["open_pages_urls"]) == 2
assert obs["active_page_index"] == 1
obs, reward, term, trunc, info = env.step("page.close()")
assert len(obs["open_pages_urls"]) == 1
assert obs["active_page_index"] == 0
env.close()
def test_nested_iframes_default_demo_mode():
demo_mode = "default"
action_set = HighLevelActionSet(demo_mode=demo_mode)
env = gym.make(
"browsergym/openended",
task_kwargs={"start_url": BASIC_IFRAME_PAGE},
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"]
soup = bs4.BeautifulSoup(flatten_dom_to_str(obs["dom_object"]), "lxml")
inner_checkbox = soup.find("input", attrs={"id": "checkbox_2"})
assert inner_checkbox.has_attr("checked")
# click box
action = f"""\
click({repr(inner_checkbox.get(BID_ATTR))})
"""
click_start = time()
obs, _, _, _, _ = env.step(action)
click_end = time()
# clicking should be slow in demo mode
assert click_end - click_start > 1
soup = bs4.BeautifulSoup(flatten_dom_to_str(obs["dom_object"]), "lxml")
inner_checkbox = soup.find("input", attrs={"id": "checkbox_2"})
# box is not checked; meaning it was clicked by the previous action
assert not inner_checkbox.has_attr("checked")
env.close()
@pytest.mark.parametrize("global_demo_mode", [True, False])
@pytest.mark.parametrize("demo_mode", [None, "off", "default", "only_visible_elements", "all_blue"])
def test_demo_mode(global_demo_mode: bool, demo_mode: str):
action_set = HighLevelActionSet(demo_mode=demo_mode)
browsergym.core.action.set_global_demo_mode(global_demo_mode)
demo_mode_active = (global_demo_mode and demo_mode is None) or (
demo_mode is not None and demo_mode != "off"
)
env = gym.make(
"browsergym/openended",
task_kwargs={"start_url": TEST_PAGE},
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"]
soup = bs4.BeautifulSoup(flatten_dom_to_str(obs["dom_object"]), "lxml")
email_field = soup.find("input", attrs={"id": "email"})
checkbox = soup.find("input", attrs={"id": "subscribe"})
# check that the email field is empty
assert email_field.get("value") == ""
# check that the box is not checked
assert not checkbox.has_attr("checked")
# click box
action = f"""\
click({repr(checkbox.get(BID_ATTR))})
"""
obs, reward, terminated, truncated, 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": "subscribe"})
# check that the box is checked
assert checkbox.has_attr("checked")
# clicking should be slow (only in demo mode)
action_time = info["action_exec_stop"] - info["action_exec_start"]
if demo_mode_active:
assert action_time > 2
else:
assert action_time <= 1.5
# fill box
action = f"""\
fill({repr(email_field.get(BID_ATTR))}, "test@test")
"""
obs, reward, terminated, truncated, info = env.step(action)
assert not obs["last_action_error"]
soup = bs4.BeautifulSoup(flatten_dom_to_str(obs["dom_object"]), "lxml")
# email field has been filled correctly
email_field = soup.find("input", attrs={"id": "email"})
assert email_field.get("value") == "test@test"
# typing should be slow (only in demo mode)
action_time = info["action_exec_stop"] - info["action_exec_start"]
if demo_mode_active:
assert action_time > 2
else:
assert action_time <= 1.5
env.close()
@pytest.mark.parametrize("resizeable_window", (True, False))
@pytest.mark.parametrize("size", ((1600, 1200), (800, 800)))
def test_resizeable_window(resizeable_window, size):
env = gym.make(
"browsergym/openended",
task_kwargs={"start_url": TEST_PAGE},
headless=__HEADLESS,
slow_mo=__SLOW_MO,
timeout=__TIMEOUT,
viewport={"width": size[0], "height": size[1]},
resizeable_window=resizeable_window,
)
obs, info = env.reset()
assert not obs["last_action_error"]
assert (obs["screenshot"].shape[1], obs["screenshot"].shape[0]) == size
env.close()