aboutsummaryrefslogtreecommitdiff
path: root/test/haketilo_test/extension_crafting.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/haketilo_test/extension_crafting.py')
-rw-r--r--test/haketilo_test/extension_crafting.py215
1 files changed, 215 insertions, 0 deletions
diff --git a/test/haketilo_test/extension_crafting.py b/test/haketilo_test/extension_crafting.py
new file mode 100644
index 0000000..97f5027
--- /dev/null
+++ b/test/haketilo_test/extension_crafting.py
@@ -0,0 +1,215 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+"""
+Making temporary WebExtensions for use in the test suite
+"""
+
+# This file is part of Haketilo.
+#
+# Copyright (C) 2021, 2022 Wojtek Kosior <koszko@koszko.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+# I, Wojtek Kosior, thereby promise not to sue for violation of this file's
+# license. Although I request that you do not make use of this code in a
+# proprietary program, I am not going to enforce this in court.
+
+import json
+import zipfile
+import re
+import shutil
+import subprocess
+
+from pathlib import Path
+from uuid import uuid4
+from tempfile import TemporaryDirectory
+
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.common.exceptions import NoSuchElementException
+
+from .misc_constants import *
+
+class ManifestTemplateValueToFill:
+ pass
+
+def manifest_template():
+ return {
+ 'manifest_version': 2,
+ 'name': 'Haketilo test extension',
+ 'version': '1.0',
+ 'applications': {
+ 'gecko': {
+ 'id': ManifestTemplateValueToFill(),
+ 'strict_min_version': '60.0'
+ }
+ },
+ 'permissions': [
+ 'contextMenus',
+ 'webRequest',
+ 'webRequestBlocking',
+ 'activeTab',
+ 'notifications',
+ 'sessions',
+ 'storage',
+ 'tabs',
+ '<all_urls>',
+ 'unlimitedStorage'
+ ],
+ 'content_security_policy': "object-src 'none'; script-src 'self' https://serve.scrip.ts;",
+ 'web_accessible_resources': ['testpage.html'],
+ 'options_ui': {
+ 'page': 'testpage.html',
+ 'open_in_tab': True
+ },
+ 'background': {
+ 'persistent': True,
+ 'scripts': ['__open_test_page.js', 'background.js']
+ },
+ 'content_scripts': [
+ {
+ 'run_at': 'document_start',
+ 'matches': ['<all_urls>'],
+ 'match_about_blank': True,
+ 'all_frames': True,
+ 'js': ['content.js']
+ }
+ ]
+ }
+
+class ExtraHTML:
+ def __init__(self, html_path, append={}, wrap_into_htmldoc=True):
+ self.html_path = html_path
+ self.append = append
+ self.wrap_into_htmldoc = wrap_into_htmldoc
+
+ def add_to_xpi(self, xpi, tmpdir=None):
+ if tmpdir is None:
+ with TemporaryDirectory() as tmpdir:
+ return self.add_to_xpi(xpi, tmpdir)
+
+ append_flags = []
+ for filename, code in self.append.items():
+ append_flags.extend(['-A', f'{filename}:{code}'])
+
+ awk = subprocess.run(
+ ['awk', '-f', awk_script_name, '--', *unit_test_defines,
+ *append_flags, '-H', self.html_path, '--write-js-deps',
+ '--output=files-to-copy', f'--output-dir={tmpdir}'],
+ stdout=subprocess.PIPE, cwd=proj_root, check=True
+ )
+
+ for path in filter(None, awk.stdout.decode().split('\n')):
+ xpi.write(proj_root / path, path)
+
+ tmpdir = Path(tmpdir)
+ for path in tmpdir.rglob('*'):
+ relpath = str(path.relative_to(tmpdir))
+ if not path.is_dir() and relpath != self.html_path:
+ xpi.write(path, relpath)
+
+ with open(tmpdir / self.html_path, 'rt') as html_file:
+ html = html_file.read()
+ if self.wrap_into_htmldoc:
+ html = f'<!DOCTYPE html><html><body>{html}</body></html>'
+ xpi.writestr(self.html_path, html)
+
+default_background_script = ''
+default_content_script = ''
+default_test_page = '''
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Extension's options page for testing</title>
+ </head>
+ <body>
+ <h1>Extension's options page for testing</h1>
+ </body>
+</html>
+'''
+
+open_test_page_script = '''(() => {
+const page_url = browser.runtime.getURL("testpage.html");
+const execute_details = {
+ code: `window.wrappedJSObject.ext_page_url=${JSON.stringify(page_url)};`
+};
+browser.tabs.query({currentWindow: true, active: true})
+ .then(t => browser.tabs.executeScript(t.id, execute_details));
+})();'''
+
+def make_extension(destination_dir,
+ background_script=default_background_script,
+ content_script=default_content_script,
+ test_page=default_test_page,
+ extra_files={}, extra_html=[]):
+ if not hasattr(extra_html, '__iter__'):
+ extra_html = [extra_html]
+ manifest = manifest_template()
+ extension_id = '{%s}' % uuid4()
+ manifest['applications']['gecko']['id'] = extension_id
+ files = {
+ 'manifest.json' : json.dumps(manifest),
+ '__open_test_page.js': open_test_page_script,
+ 'background.js' : background_script,
+ 'content.js' : content_script,
+ 'testpage.html' : test_page,
+ **extra_files
+ }
+ destination_path = destination_dir / f'{extension_id}.xpi'
+ with zipfile.ZipFile(destination_path, 'x') as xpi:
+ for filename, contents in files.items():
+ if hasattr(contents, '__call__'):
+ contents = contents()
+ xpi.writestr(filename, contents)
+ for html in extra_html:
+ html.add_to_xpi(xpi)
+
+ return destination_path
+
+extract_base_url_re = re.compile(r'^(.*)manifest.json$')
+
+def get_extension_base_url(driver):
+ """
+ Extension's internall UUID is not directly exposed in Selenium. Instead, we
+ can navigate to about:debugging and inspect the manifest URL present there
+ to get the base url like:
+ moz-extension://b225c78f-d108-4caa-8406-f38b37d8dee5/
+ which can then be used to navigate to extension-bundled pages.
+ """
+ # For newer Firefoxes
+ driver.get('about:debugging#/runtime/this-firefox')
+
+ def get_manifest_link_newer_ff(driver):
+ try:
+ return driver.find_element_by_class_name('qa-manifest-url')
+ except NoSuchElementException:
+ pass
+
+ try:
+ details = driver.find_element_by_class_name('error-page-details')
+ except NoSuchElementException:
+ return False
+
+ if '#/runtime/this-firefox' in details.text:
+ return "not_newer_ff"
+
+ manifest_link = WebDriverWait(driver, 10).until(get_manifest_link_newer_ff)
+
+ if manifest_link == "not_newer_ff":
+ driver.get("about:debugging#addons")
+ driver.implicitly_wait(10)
+ manifest_link = driver.find_element_by_class_name('manifest-url')
+ driver.implicitly_wait(0)
+
+ manifest_url = manifest_link.get_attribute('href')
+ return extract_base_url_re.match(manifest_url).group(1)