Skip to content

Commit

Permalink
end2end tests: migrate more stuff into addon helper
Browse files Browse the repository at this point in the history
  • Loading branch information
karlicoss committed May 18, 2024
1 parent f77c6d7 commit 235cc10
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 27 deletions.
4 changes: 2 additions & 2 deletions extension/src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -1048,10 +1048,10 @@ function initBackground(): void {

chrome.runtime.onMessage.addListener((info: any, _: chrome$MessageSender) => {
// see selenium_bridge.js
if (info === 'selenium-bridge-activate') {
if (info === 'selenium-bridge-_execute_browser_action') {
handleToggleSidebar()
}
if (info === 'selenium-bridge-mark-visited') {
if (info === 'selenium-bridge-mark_visited') {
handleToggleMarkVisited()
}
if (info === 'selenium-bridge-search') {
Expand Down
4 changes: 2 additions & 2 deletions extension/src/selenium_bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

// hack to hook into the extension... https://stackoverflow.com/a/38554438/706389
for (const x of [
'selenium-bridge-activate',
'selenium-bridge-mark-visited',
'selenium-bridge-_execute_browser_action',
'selenium-bridge-mark_visited',
'selenium-bridge-search',
]) {
document.addEventListener(x, () => {
Expand Down
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ def main() -> None:

'mypy',
'lxml', # for coverage reports

'loguru',
],
'testing-gui': [
# pyautogui seems problematic, wheels often fail to build under windows
Expand Down
51 changes: 48 additions & 3 deletions tests/addon_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,31 @@
from functools import cached_property
import json
from pathlib import Path
import subprocess
import re
import subprocess
from typing import Any


from loguru import logger
import psutil
from selenium import webdriver

from webdriver_utils import is_headless


@dataclass
class AddonHelper:
driver: webdriver.Remote
addon_source: Path

@cached_property
def addon_id(self) -> str:
return get_addon_id(driver=self.driver)

@cached_property
def manifest(self) -> Any:
# ugh. sadly (at least in Firefox) there doesn't seem a way to read actual manifest loaded in browser?
return json.loads((self.addon_source / 'manifest.json').read_text())

@property
def extension_prefix(self) -> str:
protocol = {
Expand All @@ -29,6 +38,42 @@ def extension_prefix(self) -> str:
def open_page(self, path: str) -> None:
self.driver.get(self.extension_prefix + '/' + path)

@property
def options_page_name(self) -> str:
return self.manifest['options_ui']['page']

@property
def headless(self) -> bool:
return is_headless(self.driver)

def trigger_command(self, command: str) -> None:
commands = self.manifest['commands']
assert command in commands, (command, commands)

if self.headless:
# see selenium_bridge.js
ccc = f'selenium-bridge-{command}'
self.driver.execute_script(
f"""
var event = document.createEvent('HTMLEvents');
event.initEvent('{ccc}', true, true);
document.dispatchEvent(event);
"""
)
else:
hotkey = commands[command]['suggested_key']['default']
self.trigger_hotkey(hotkey)

def trigger_hotkey(self, key: str) -> None:
assert not self.headless # just in case
lkey = key.lower().split('+')
logger.debug(f'sending hotkey {lkey}')

import pyautogui

focus_browser_window(self.driver)
pyautogui.hotkey(*lkey)


# NOTE looks like it used to be posssible in webdriver api?
# at least as of 2011 https://github.com/gavinp/chromium/blob/681563ea0f892a051f4ef3d5e53438e0bb7d2261/chrome/test/webdriver/test/chromedriver.py#L35-L40
Expand Down Expand Up @@ -114,6 +159,6 @@ def has_wm_desktop(wid: str) -> bool:


def focus_browser_window(driver: webdriver.Remote) -> None:
# FIXME assert not is_headless(driver) # just in case
assert not is_headless(driver) # just in case
wid = get_window_id(driver)
subprocess.check_call(['xdotool', 'windowactivate', '--sync', wid])
31 changes: 11 additions & 20 deletions tests/end2end_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@

from common import under_ci, has_x, local_http_server, notnone
from browser_helper import open_extension_page, get_cmd_hotkey
from webdriver_utils import frame_context, window_context, is_visible
from webdriver_utils import frame_context, is_visible, is_headless
from addon_helper import AddonHelper, focus_browser_window, get_window_id


Expand Down Expand Up @@ -222,7 +222,9 @@ def set_checkbox(cid: str, value: bool) -> None:
# TODO log properly
print(f"Setting: port {port}, show_dots {show_dots}")

addon = Addon(helper=AddonHelper(driver=driver))
addon_source = get_addon_path(kind=driver.name)
helper = AddonHelper(driver=driver, addon_source=addon_source)
addon = Addon(helper=helper)
page = addon.options_page
page.open()

Expand Down Expand Up @@ -288,16 +290,6 @@ def send_key(key) -> None:
pyautogui.hotkey(*key)


def is_headless(driver: Driver) -> bool:
if driver.name == 'firefox':
return driver.capabilities.get('moz:headless', False)
elif driver.name == 'chrome':
# https://antoinevastel.com/bot%20detection/2018/01/17/detect-chrome-headless-v2.html
return driver.execute_script("return navigator.webdriver") is True
else:
raise RuntimeError(driver.name)


# TODO move to common or something
def trigger_hotkey(driver: Driver, hotkey: str) -> None:
headless = is_headless(driver)
Expand All @@ -313,8 +305,8 @@ def trigger_hotkey(driver: Driver, hotkey: str) -> None:
def trigger_command(driver: Driver, cmd: str) -> None:
if is_headless(driver):
ccc = {
Command.ACTIVATE : 'selenium-bridge-activate',
Command.MARK_VISITED: 'selenium-bridge-mark-visited',
Command.ACTIVATE : 'selenium-bridge-_execute_browser_action',
Command.MARK_VISITED: 'selenium-bridge-mark_visited',
Command.SEARCH : 'selenium-bridge-search',
}[cmd]
# see selenium_bridge.js
Expand Down Expand Up @@ -460,10 +452,7 @@ class OptionsPage2:
helper: AddonHelper

def open(self) -> None:
# TODO extract from manifest -> options_id -> options.html
# seems like addon just links to the actual manifest on filesystem, so will need to read from that
page_name = 'options_page.html'
self.helper.open_page(page_name)
self.helper.open_page(self.helper.options_page_name)

# make sure settings are loaded first -- otherwise we might get race conditions when we try to set them in tests
Wait(self.helper.driver, timeout=5).until(
Expand Down Expand Up @@ -497,8 +486,9 @@ class AddonHelperX:

delegate: AddonHelper

# can remove later, this is just hack for Addon.sidebar
def activate(self) -> None:
trigger_command(self.delegate.driver, Command.ACTIVATE)
self.delegate.trigger_command(Command.ACTIVATE)

def __getattr__(self, name: str) -> Any:
return getattr(self.delegate, name)
Expand Down Expand Up @@ -701,7 +691,8 @@ def driver(browser: Browser) -> Iterator[Driver]:

@pytest.fixture
def addon(driver: Driver) -> Iterator[Addon]:
helper = AddonHelper(driver)
addon_source = get_addon_path(kind=driver.name)
helper = AddonHelper(driver=driver, addon_source=addon_source)
yield Addon(helper=helper)


Expand Down
10 changes: 10 additions & 0 deletions tests/webdriver_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,13 @@ def is_visible(driver: Driver, element: WebElement) -> bool:
# (returning true for elements that aren't displayed)
# it seems to even differ between browsers
return driver.execute_script('return arguments[0].checkVisibility()', element)


def is_headless(driver: Driver) -> bool:
if driver.name == 'firefox':
return driver.capabilities.get('moz:headless', False)
elif driver.name == 'chrome':
# https://antoinevastel.com/bot%20detection/2018/01/17/detect-chrome-headless-v2.html
return driver.execute_script("return navigator.webdriver") is True
else:
raise RuntimeError(driver.name)

0 comments on commit 235cc10

Please sign in to comment.