Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SoM bugfix #185

Merged
merged 6 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 30 additions & 27 deletions browsergym/core/src/browsergym/utils/obs.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import ast
import logging
import math
import re
from collections import defaultdict

import numpy as np
import PIL.Image
import PIL.ImageDraw
import PIL.ImageFont
import re

from collections import defaultdict
from bs4 import BeautifulSoup

from browsergym.core.constants import BROWSERGYM_ID_ATTRIBUTE as BID_ATTR
from browsergym.core.constants import BROWSERGYM_VISIBILITY_ATTRIBUTE as VIS_ATTR
from browsergym.core.constants import BROWSERGYM_SETOFMARKS_ATTRIBUTE as SOM_ATTR
from browsergym.core.constants import BROWSERGYM_VISIBILITY_ATTRIBUTE as VIS_ATTR

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -434,31 +435,33 @@ def overlay_som(

font = PIL.ImageFont.load_default(size=fontsize)

# https://stackoverflow.com/questions/51908563/dotted-or-dashed-line-with-python-pillow/58885306#58885306
import math # math has the fastest sqrt

def linedashed(draw: PIL.ImageDraw.Draw, x0, y0, x1, y1, fill, width, dashlen=4, ratio=3):
dx = x1 - x0 # delta x
dy = y1 - y0 # delta y
# check whether we can avoid sqrt
if dy == 0:
vlen = dx
elif dx == 0:
vlen = dy
else:
vlen = math.sqrt(dx * dx + dy * dy) # length of line
xa = dx / vlen # x add for 1px line length
ya = dy / vlen # y add for 1px line length
step = dashlen * ratio # step to the next dash
a0 = 0
while a0 < vlen:
a1 = a0 + dashlen
if a1 > vlen:
a1 = vlen
# Adapted from https://stackoverflow.com/questions/51908563/dotted-or-dashed-line-with-python-pillow/58885306#58885306
def linedashed(
draw: PIL.ImageDraw.Draw, x0, y0, x1, y1, fill, width, dash_length=4, nodash_length=8
):
line_dx = x1 - x0 # delta x (can be negative)
line_dy = y1 - y0 # delta y (can be negative)
line_length = math.hypot(line_dx, line_dy) # line length (positive)
if line_length == 0:
return # Avoid division by zero in case the line length is 0
pixel_dx = line_dx / line_length # x add for 1px line length
pixel_dy = line_dy / line_length # y add for 1px line length
dash_start = 0
while dash_start < line_length:
dash_end = dash_start + dash_length
if dash_end > line_length:
dash_end = line_length
draw.line(
(x0 + xa * a0, y0 + ya * a0, x0 + xa * a1, y0 + ya * a1), fill=fill, width=width
(
round(x0 + pixel_dx * dash_start),
round(y0 + pixel_dy * dash_start),
round(x0 + pixel_dx * dash_end),
round(y0 + pixel_dy * dash_end),
),
fill=fill,
width=width,
)
a0 += step
dash_start += dash_length + nodash_length

for bid, properties in extra_properties.items():
if properties["set_of_marks"] and properties["bbox"]:
Expand Down
26 changes: 13 additions & 13 deletions tests/core/test_gym_envs.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import bs4
import gymnasium as gym
import os
import pathlib
from time import time

import bs4
import gymnasium as gym
import pytest

# register openended gym environments
import browsergym.core
from browsergym.core.action.highlevel import HighLevelActionSet
from browsergym.core.action.python import PythonActionSet
from browsergym.utils.obs import flatten_dom_to_str
from browsergym.core.constants import BROWSERGYM_ID_ATTRIBUTE as BID_ATTR
from time import time, sleep

# register openended gym environments
import browsergym.core
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
Expand Down Expand Up @@ -250,17 +250,17 @@ def test_demo_mode(demo_mode):
# typing should be slow in demo mode
assert typing_end - typing_start > 1

sleep(1)

# email field has been filled correctly
assert env.page.input_value("#email") == "test@test"
# wait for the typing to complete

soup = bs4.BeautifulSoup(flatten_dom_to_str(obs["dom_object"]), "lxml")
checkbox = soup.find("input", attrs={"id": "subscribe"})
# box is not checked
assert not checkbox.has_attr("checked")

# email field has been filled correctly
email_field = soup.find("input", attrs={"id": "email"})

# check that the email field is empty
assert email_field.get("value") == "test@test"

# click box
action = f"""\
click({repr(checkbox.get(BID_ATTR))})
Expand Down
40 changes: 18 additions & 22 deletions tests/core/test_observation.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
import ast
import os
from pathlib import Path

import bs4
import gymnasium as gym
import numpy as np
import os
from pathlib import Path
import pytest
import regex as re

# register gym environments
import browsergym.core

from browsergym.utils.obs import (
flatten_axtree_to_str,
flatten_dom_to_str,
)

from browsergym.core.constants import BROWSERGYM_ID_ATTRIBUTE as BID_ATTR
from browsergym.core.observation import (
_pre_extract,
_post_extract,
_pre_extract,
extract_all_frame_axtrees,
extract_dom_snapshot,
extract_merged_axtree,
extract_screenshot,
)
from browsergym.core.constants import BROWSERGYM_ID_ATTRIBUTE as BID_ATTR
from browsergym.utils.obs import flatten_axtree_to_str, 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
Expand Down Expand Up @@ -555,7 +551,7 @@ def test_simple_webpage():
)
obs, info = env.reset()

element = env.page.query_selector('[type="checkbox"]')
element = env.unwrapped.page.query_selector('[type="checkbox"]')

assert not element.is_checked()

Expand All @@ -569,9 +565,9 @@ def test_simple_webpage():
x, y = map(float, ast.literal_eval(input_elem.get("center")))

# click input elem
env.page.mouse.click(x, y)
env.unwrapped.page.mouse.click(x, y)

element = env.page.query_selector('[type="checkbox"]')
element = env.unwrapped.page.query_selector('[type="checkbox"]')

assert element.is_checked()

Expand All @@ -596,7 +592,7 @@ def test_basic_iframe_webpage():
# click on the checkbox in the main frame
obs, info = env.reset()

element = env.page.query_selector('[type="checkbox"]')
element = env.unwrapped.page.query_selector('[type="checkbox"]')

assert not element.is_checked()

Expand All @@ -609,13 +605,13 @@ def test_basic_iframe_webpage():
)
input_elem = soup.find("input", attrs={"bid": bid})
x, y = map(float, ast.literal_eval(input_elem.get("center")))
env.page.mouse.click(x, y)
env.unwrapped.page.mouse.click(x, y)

assert element.is_checked()

# click on the checkbox in the inner_frame
obs, _, _, _, _ = env.step("")
element = env.page.frames[2].query_selector('[type="checkbox"]')
element = env.unwrapped.page.frames[2].query_selector('[type="checkbox"]')

assert element.is_checked() # instantiated as checked

Expand All @@ -628,14 +624,14 @@ def test_basic_iframe_webpage():
)
input_elem = soup.find("input", attrs={"bid": bid})
x, y = map(float, ast.literal_eval(input_elem.get("center")))
env.page.mouse.click(x, y)
env.unwrapped.page.mouse.click(x, y)

assert not element.is_checked()

# scroll inside a frame, and click on the checkbox in the inner_frame
env.page.frames[1].evaluate("window.scrollTo(0, document.body.scrollHeight);")
env.unwrapped.page.frames[1].evaluate("window.scrollTo(0, document.body.scrollHeight);")
obs, _, _, _, _ = env.step("")
element = env.page.frames[2].query_selector('[type="checkbox"]')
element = env.unwrapped.page.frames[2].query_selector('[type="checkbox"]')

assert not element.is_checked() # instantiated as checked

Expand All @@ -648,7 +644,7 @@ def test_basic_iframe_webpage():
)
input_elem = soup.find("input", attrs={"bid": bid})
x, y = map(float, ast.literal_eval(input_elem.get("center")))
env.page.mouse.click(x, y)
env.unwrapped.page.mouse.click(x, y)

assert element.is_checked()
env.close()
Expand All @@ -672,10 +668,10 @@ def test_filter_visible_only():
assert "textbox" not in axtree_txt

# scroll on the main frame, then scroll inside a frame to find that hidden textbox element
env.page.evaluate(
env.unwrapped.page.evaluate(
"window.scrollTo(document.body.scrollWidth / 3, document.body.scrollHeight / 3);"
)
iframe = env.page.frames[1]
iframe = env.unwrapped.page.frames[1]
iframe.evaluate("window.scrollTo(0, document.body.scrollHeight / 3.5);")

obs, _, _, _, _ = env.step("")
Expand Down