-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
More work on manifest v3 compatibility and proper end-to-end tests
Some changes - default capture shotcut changed to ctlr+{alt/shift}+h (since it doesn't conflict with anything else) - better error handling in extension
- Loading branch information
Showing
10 changed files
with
510 additions
and
141 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 was deleted.
Oops, something went wrong.
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.
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,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]) |
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,14 @@ | ||
import pytest | ||
|
||
|
||
def pytest_addoption(parser): | ||
parser.addoption( | ||
'--browser', | ||
choices=['firefox', 'chrome'], | ||
default='firefox', # FIXME add --all or something | ||
) | ||
|
||
|
||
@pytest.fixture | ||
def browser(request) -> str: | ||
return request.config.getoption("--browser") |
Oops, something went wrong.