-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
After trying out the devtools protocols in BugHog, the two simple implementations I had prepared worked only for Chromium 70+ and Firefox 127+. Even though there were some other protocols implemented in previous browser versions, it would require a lot of effort to implement all of these and correctly match them to the supported browser versions. Further, there would likely be no way to target versions approx. 20-50. As a result, I decided to use PyAutoGUI as you suggested. It works in all browsers and versions and for practical usage it seems almost as good as the devtools protocols. Instead of `url_queue.txt`, users can choose to provide the experiment configuration in `interaction_script.cmd`. These are the commands implemented so far: - `NAVIGATE url` Terminates the previous browser window and opens a new browser window on the specified URL. Further, it waits some time (1 sec for Chromium, 2 secs for Firefox) for the page to load. - `CLICK_POSITION x y` Clicks on specific coordinates on the screen (not necessarily the browser window), The argument values can be absolute in pixels, percentage of the screen, or a combination of both - e.g., `CLICK_POSITION 100 50%` clicks 100px from the left screen border and 50% of the screen height. - `CLICK element_id` Clicks on an element with the specified ID. Currently, the ID can be one of `one`, `two`, `three`, `four`, `five`, `six`. This is because PyAutoGUI can search for the location of a visual match on the screen. Therefore, I prepared styles in `res/bughog.css` that style elements with these IDs to boxes of distinct colors. This allows us to bypass the limitation of having to know the exact screen coordinates of an element. - `WRITE text` Types the text into the focused element. - `PRESS key` - `HOLD key` - `RELEASE key` - `HOTKEY key1 key2 ...` A combination of `HOLD` and `RELEASE` for multiple keys. E.g., `HOTKEY ctrl c`. - `SLEEP seconds` Usually should not be necessary to use because navigation implicitly includes sleeping. - `SCREENSHOT file_name` Captures the screen and stores the result in `logs/screenshots/{PROJECT}-{EXPERIMENT}-{file_name}-{BROWSER}-{VERSION}.jpg`. Very useful for debugging A simple experiment can be found in `Support/AutoGUI`. It got successfully reproduced in all versions of both browsers. We can possibly implement some browser-specific behaviour as well, e.g., bookmarking a string where the script would include only `BOOKMARK text` and based on the browser version, the correct shortcut would be pressed and screen positions clicked. Besides this, I made the following changes: - Extracted the default file templates to separate files and added templates for all file types - Implemented adding and modifying `interaction_script.cmd` and `url_queue.txt` from the web UI - Implemented a custom highlighting mode for `interaction_script.cmd` in the experiment editor - Fixed loading resources from `/res/`
- Loading branch information
Showing
39 changed files
with
667 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import logging | ||
from inspect import signature | ||
|
||
from bci.browser.configuration.browser import Browser as BrowserConfig | ||
from bci.browser.interaction.simulation import Simulation | ||
from bci.evaluations.logic import TestParameters | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class Interaction: | ||
browser: BrowserConfig | ||
script: list[str] | ||
params: TestParameters | ||
|
||
def __init__(self, browser: BrowserConfig, script: list[str], params: TestParameters) -> None: | ||
self.browser = browser | ||
self.script = script | ||
self.params = params | ||
|
||
def execute(self) -> None: | ||
simulation = Simulation(self.browser, self.params) | ||
|
||
if self._interpret(simulation): | ||
simulation.sleep(str(self.browser.get_navigation_sleep_duration())) | ||
simulation.navigate('https://a.test/report/?bughog_sanity_check=OK') | ||
|
||
def _interpret(self, simulation: Simulation) -> bool: | ||
for statement in self.script: | ||
if statement.strip() == '' or statement[0] == '#': | ||
continue | ||
|
||
cmd, *args = statement.split() | ||
method_name = cmd.lower() | ||
|
||
if method_name not in Simulation.public_methods: | ||
raise Exception( | ||
f'Invalid command `{cmd}`. Expected one of {", ".join(map(lambda m: m.upper(), Simulation.public_methods))}.' | ||
) | ||
|
||
method = getattr(simulation, method_name) | ||
method_params = list(signature(method).parameters.values()) | ||
|
||
# Allow different number of arguments only for variable argument number (*) | ||
if len(method_params) != len(args) and (len(method_params) < 1 or str(method_params[0])[0] != '*'): | ||
raise Exception( | ||
f'Invalid number of arguments for command `{cmd}`. Expected {len(method_params)}, got {len(args)}.' | ||
) | ||
|
||
logger.debug(f'Executing interaction method `{method_name}` with the arguments {args}') | ||
|
||
try: | ||
method(*args) | ||
except: | ||
return False | ||
|
||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import os | ||
from time import sleep | ||
|
||
import pyautogui as gui | ||
import Xlib.display | ||
from pyvirtualdisplay.display import Display | ||
|
||
from bci.browser.configuration.browser import Browser as BrowserConfig | ||
from bci.evaluations.logic import TestParameters | ||
|
||
|
||
class Simulation: | ||
browser_config: BrowserConfig | ||
params: TestParameters | ||
|
||
public_methods: list[str] = [ | ||
'navigate', | ||
'click_position', | ||
'click', | ||
'write', | ||
'press', | ||
'hold', | ||
'release', | ||
'hotkey', | ||
'sleep', | ||
'screenshot', | ||
] | ||
|
||
def __init__(self, browser_config: BrowserConfig, params: TestParameters): | ||
self.browser_config = browser_config | ||
self.params = params | ||
disp = Display(visible=True, size=(1920, 1080), backend='xvfb', use_xauth=True) | ||
disp.start() | ||
gui._pyautogui_x11._display = Xlib.display.Display(os.environ['DISPLAY']) | ||
|
||
def __del__(self): | ||
self.browser_config.terminate() | ||
|
||
def parse_position(self, position: str, max_value: int) -> int: | ||
# Screen percentage | ||
if position[-1] == '%': | ||
return round(max_value * (int(position[:-1]) / 100)) | ||
|
||
# Absolute value in pixels | ||
return int(position) | ||
|
||
# --- PUBLIC METHODS --- | ||
def navigate(self, url: str): | ||
self.browser_config.terminate() | ||
self.browser_config.open(url) | ||
self.sleep(str(self.browser_config.get_navigation_sleep_duration())) | ||
|
||
def click_position(self, x: str, y: str): | ||
max_x, max_y = gui.size() | ||
|
||
gui.moveTo(self.parse_position(x, max_x), self.parse_position(y, max_y)) | ||
gui.click() | ||
|
||
def click(self, el_id: str): | ||
el_image_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), f'elements/{el_id}.png') | ||
x, y = gui.locateCenterOnScreen(el_image_path) | ||
self.click_position(str(x), str(y)) | ||
|
||
def write(self, text: str): | ||
gui.write(text, interval=0.1) | ||
|
||
def press(self, key: str): | ||
gui.press(key) | ||
|
||
def hold(self, key: str): | ||
gui.keyDown(key) | ||
|
||
def release(self, key: str): | ||
gui.keyUp(key) | ||
|
||
def hotkey(self, *keys: str): | ||
gui.hotkey(*keys) | ||
|
||
def sleep(self, duration: str): | ||
sleep(float(duration)) | ||
|
||
def screenshot(self, filename: str): | ||
filename = f'{self.params.evaluation_configuration.project}-{self.params.mech_group}-{filename}-{type(self.browser_config).__name__}-{self.browser_config.version}.jpg' | ||
filepath = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../../logs/screenshots', filename) | ||
gui.screenshot(filepath) |
Oops, something went wrong.