Skip to content

Commit

Permalink
end2end tests: add addon helper from grasp, start migrating/unifying
Browse files Browse the repository at this point in the history
  • Loading branch information
karlicoss committed May 18, 2024
1 parent 813c4a6 commit f77c6d7
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 69 deletions.
119 changes: 119 additions & 0 deletions tests/addon_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from dataclasses import dataclass
from functools import cached_property
import json
from pathlib import Path
import subprocess
import re


import psutil
from selenium import webdriver


@dataclass
class AddonHelper:
driver: webdriver.Remote

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

@property
def extension_prefix(self) -> str:
protocol = {
'chrome': 'chrome-extension',
'firefox': 'moz-extension',
}[self.driver.name]
return f'{protocol}://{self.addon_id}'

def open_page(self, path: str) -> None:
self.driver.get(self.extension_prefix + '/' + path)


# 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
# but here https://github.com/SeleniumHQ/selenium/blob/master/cpp/webdriver-server/command_types.h there are no Extension commands
# also see driver.command_executor._commands
def _get_chrome_addon_id(driver: webdriver.Remote) -> str:
"""
For a temporary addon extension id is autogenerated, so we need to extract it every time
"""
user_data_dir = Path(driver.capabilities['chrome']['userDataDir'])
prefs_file = user_data_dir / 'Default/Preferences'
assert prefs_file.exists(), prefs_file

# for some idiotic reason, after chrome launches, extension settings aren't immediately available
# this can take up to 30 secons in this loop until they are populated
while True:
prefs = json.loads(prefs_file.read_text())
extension_settings = prefs.get('extensions', {}).get('settings', None)
if extension_settings is not None:
# there are some other weird builtin extension as well
# this seems like the easiest way to filter them out extracing by extension name or path
[addon_id] = [k for k, v in extension_settings.items() if v['creation_flags'] != 1]
return addon_id


def _get_firefox_addon_id(driver: webdriver.Remote) -> str:
moz_profile = Path(driver.capabilities['moz:profile'])
prefs_file = moz_profile / 'prefs.js'
assert prefs_file.exists(), prefs_file

while True:
for line in prefs_file.read_text().splitlines():
m = re.fullmatch(r'user_pref\("extensions.webextensions.uuids", "(.*)"\);', line)
if m is None:
continue
# this contains a json with escaped quotes
user_prefs_s = m.group(1).replace('\\', '')
user_prefs = json.loads(user_prefs_s)
addon_ids = [v for k, v in user_prefs.items() if 'mozilla.' not in k]
if len(addon_ids) == 0:
# for some stupid reason it doesn't appear immediately in the file
continue
[addon_id] = addon_ids
return addon_id


def get_addon_id(driver: webdriver.Remote) -> str:
extractor = {
'firefox': _get_firefox_addon_id,
'chrome': _get_chrome_addon_id,
}[driver.name]
return extractor(driver)


def get_window_id(driver: webdriver.Remote) -> str:
if driver.name == 'firefox':
pid = str(driver.capabilities['moz:processID'])
elif driver.name == 'chrome':
# ugh no pid in capabilities...
driver_pid = driver.service.process.pid # type: ignore[attr-defined]
process = psutil.Process(driver_pid)
[chrome_process] = process.children()
cmdline = chrome_process.cmdline()
assert '--enable-automation' in cmdline, cmdline
pid = str(chrome_process.pid)
else:
raise RuntimeError(driver.name)
return get_wid_by_pid(pid)


def get_wid_by_pid(pid: str) -> str:
# https://askubuntu.com/a/385037/427470
wids = subprocess.check_output(['xdotool', 'search', '--pid', pid]).decode('utf8').splitlines()
wids = [w.strip() for w in wids if len(w.strip()) > 0]

def has_wm_desktop(wid: str) -> bool:
# TODO no idea why is that important. found out experimentally
out = subprocess.check_output(['xprop', '-id', wid, '_NET_WM_DESKTOP']).decode('utf8')
return 'not found' not in out

[wid] = filter(has_wm_desktop, wids)
return wid


def focus_browser_window(driver: webdriver.Remote) -> None:
# FIXME assert not is_headless(driver) # just in case
wid = get_window_id(driver)
subprocess.check_call(['xdotool', 'windowactivate', '--sync', wid])
5 changes: 3 additions & 2 deletions tests/demos.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from common import uses_x
from end2end_test import FF, CH, browsers, _test_helper
from end2end_test import confirm
from end2end_test import configure, get_window_id
from end2end_test import configure_extension
from addon_helper import get_window_id

from record import record, hotkeys, CURSOR_SCRIPT, SELECT_SCRIPT

Expand Down Expand Up @@ -129,7 +130,7 @@ def set_geometry(wid: str):
if 'highlights' not in extras:
extras['highlights'] = False

configure(
configure_extension(
driver,
host=None, port=None, # TODO meh
notify_contexts=False,
Expand Down
Loading

0 comments on commit f77c6d7

Please sign in to comment.