aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--background/broadcast_broker.js184
-rw-r--r--common/broadcast.js113
-rw-r--r--common/connection_types.js4
-rw-r--r--copyright2
-rw-r--r--pytest.ini19
-rw-r--r--test/extension_crafting.py2
-rw-r--r--test/unit/conftest.py44
-rw-r--r--test/unit/test_basic.py7
-rw-r--r--test/unit/test_broadcast.py181
-rw-r--r--test/unit/test_indexeddb.py3
-rw-r--r--test/unit/test_patterns.py6
-rw-r--r--test/unit/test_patterns_query_tree.py9
12 files changed, 551 insertions, 23 deletions
diff --git a/background/broadcast_broker.js b/background/broadcast_broker.js
new file mode 100644
index 0000000..7af8769
--- /dev/null
+++ b/background/broadcast_broker.js
@@ -0,0 +1,184 @@
+/**
+ * This file is part of Haketilo.
+ *
+ * Function: Facilitate broadcasting messages between different execution
+ * contexts of the extension
+ *
+ * Copyright (C) 2021 Wojtek Kosior
+ *
+ * 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.
+ *
+ * As additional permission under GNU GPL version 3 section 7, you
+ * may distribute forms of that code without the copy of the GNU
+ * GPL normally required by section 4, provided you include this
+ * license notice and, in case of non-source distribution, a URL
+ * through which recipients can access the Corresponding Source.
+ * If you modify file(s) with this exception, you may extend this
+ * exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this
+ * exception statement from your version.
+ *
+ * As a special exception to the GPL, any HTML file which merely
+ * makes function calls to this code, and for that purpose
+ * includes it by reference shall be deemed a separate work for
+ * copyright law purposes. If you modify this code, you may extend
+ * this exception to your version of the code, but you are not
+ * obligated to do so. If you do not wish to do so, delete this
+ * exception statement from your version.
+ *
+ * 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 this code in a
+ * proprietary program, I am not going to enforce this in court.
+ */
+
+/*
+ * IMPORTS_START
+ * IMPORT listen_for_connection
+ * IMPORT CONNECTION_TYPE
+ * IMPORTS_END
+ */
+
+let next_id = 1;
+
+const listeners_by_channel = new Map();
+
+function new_broadcast_listener(port)
+{
+ listener_ctx = {port, id: ++next_id, channels: new Set()};
+ port.onMessage.addListener(msg => listener_command(msg, listener_ctx));
+ port.onDisconnect.addListener(msg => listener_remove(msg, listener_ctx));
+}
+
+function listener_command(msg, listener_ctx)
+{
+ const [disposition, name] = msg;
+
+ if (disposition === "subscribe")
+ subscribe_channel(name, listener_ctx);
+ else if (disposition === "unsubscribe")
+ unsubscribe_channel(name, listener_ctx);
+ else
+ throw `bad broadcast listener disposition '${disposition}'`;
+}
+
+function subscribe_channel(channel_name, listener_ctx)
+{
+ if (!listeners_by_channel.has(channel_name))
+ listeners_by_channel.set(channel_name, new Map());
+
+ listeners_by_channel.get(channel_name).set(listener_ctx.id, listener_ctx);
+
+ listener_ctx.channels.add(channel_name);
+}
+
+function unsubscribe_channel(channel_name, listener_ctx)
+{
+ const channel_listeners =
+ listeners_by_channel.get(channel_name) || new Map();
+ channel_listeners.delete(listener_ctx.id);
+
+ if (channel_listeners.size == 0)
+ listeners_by_channel.delete(channel_name);
+
+ listener_ctx.channels.delete(channel_name);
+}
+
+function remove_broadcast_listener(listener_ctx)
+{
+ for (const channel_name of [...listener_ctx.channels.keys()])
+ unsubscribe_channel(channel_name, listener_ctx);
+}
+
+function new_broadcast_sender(port)
+{
+ sender_ctx = {prepared_broadcasts: new Set()};
+ port.onMessage.addListener(msg => sender_command(msg, sender_ctx));
+ port.onDisconnect.addListener(msg => flush(sender_ctx));
+}
+
+function sender_command(msg, sender_ctx)
+{
+ const [disposition, name, value, timeout] = msg;
+
+ if (disposition === "prepare")
+ prepare(sender_ctx, name, value, timeout)
+ else if (disposition === "discard")
+ sender_ctx.prepared_broadcasts = new Set();
+ else if (disposition === "flush")
+ flush(sender_ctx);
+ else if (disposition === "broadcast")
+ broadcast(name, value);
+ else
+ throw `bad broadcast sender disposition '${disposition}'`;
+}
+
+function prepare(sender_ctx, channel_name, value, timeout)
+{
+ broadcast_data = [channel_name, value];
+ sender_ctx.prepared_broadcasts.add(broadcast_data);
+
+ if (timeout === 0)
+ return;
+
+ setTimeout(() => prepare_timeout_cb(sender_ctx, broadcast_data), timeout);
+}
+
+function prepare_timeout_cb(sender_ctx, broadcast_data)
+{
+ if (sender_ctx.prepared_broadcasts.has(broadcast_data)) {
+ sender_ctx.prepared_broadcasts.delete(broadcast_data);
+ broadcast(...broadcast_data);
+ }
+}
+
+function flush(sender_ctx)
+{
+ console.log('flushing', sender_ctx.prepared_broadcasts);
+ sender_ctx.prepared_broadcasts.forEach(nv => broadcast(...nv));
+ sender_ctx.prepared_broadcasts = new Set();
+}
+
+function broadcast(channel_name, value)
+{
+ const listeners = listeners_by_channel.get(channel_name);
+ if (listeners == undefined)
+ return;
+
+ for (const listener_ctx of [...listeners.values()]) {
+ try {
+ listener_ctx.port.postMessage([channel_name, value]);
+ } catch (e) {
+ console.error(e);
+ remove_broadcast_listener(listener_ctx);
+ }
+ }
+}
+
+function remove_broadcast_sender(sender_ctx)
+{
+ sender_ctx.prepared_broadcasts.forEach(nv => broadcast(...nv));
+}
+
+function start_broadcast_broker()
+{
+ listen_for_connection(CONNECTION_TYPE.BROADCAST_SEND, new_broadcast_sender);
+ listen_for_connection(CONNECTION_TYPE.BROADCAST_LISTEN,
+ new_broadcast_listener);
+}
+
+/*
+ * EXPORTS_START
+ * EXPORT start_broadcast_broker
+ * EXPORTS_END
+ */
diff --git a/common/broadcast.js b/common/broadcast.js
new file mode 100644
index 0000000..bc18103
--- /dev/null
+++ b/common/broadcast.js
@@ -0,0 +1,113 @@
+/**
+ * This file is part of Haketilo.
+ *
+ * Function: Broadcast messages to different execution contexts of the extension
+ *
+ * Copyright (C) 2021 Wojtek Kosior
+ *
+ * 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.
+ *
+ * As additional permission under GNU GPL version 3 section 7, you
+ * may distribute forms of that code without the copy of the GNU
+ * GPL normally required by section 4, provided you include this
+ * license notice and, in case of non-source distribution, a URL
+ * through which recipients can access the Corresponding Source.
+ * If you modify file(s) with this exception, you may extend this
+ * exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this
+ * exception statement from your version.
+ *
+ * As a special exception to the GPL, any HTML file which merely
+ * makes function calls to this code, and for that purpose
+ * includes it by reference shall be deemed a separate work for
+ * copyright law purposes. If you modify this code, you may extend
+ * this exception to your version of the code, but you are not
+ * obligated to do so. If you do not wish to do so, delete this
+ * exception statement from your version.
+ *
+ * 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 this code in a
+ * proprietary program, I am not going to enforce this in court.
+ */
+
+/*
+ * IMPORTS_START
+ * IMPORT CONNECTION_TYPE
+ * IMPORTS_END
+ */
+
+function sender_connection()
+{
+ return {
+ port: browser.runtime.connect({name: CONNECTION_TYPE.BROADCAST_SEND})
+ };
+}
+
+function out(sender_conn, channel_name, value)
+{
+ sender_conn.port.postMessage(["broadcast", channel_name, value]);
+}
+
+function prepare(sender_conn, channel_name, value, timeout=5000)
+{
+ sender_conn.port.postMessage(["prepare", channel_name, value, timeout]);
+}
+
+function discard(sender_conn)
+{
+ sender_conn.port.postMessage(["discard"]);
+}
+
+function flush(sender_conn)
+{
+ sender_conn.port.postMessage(["flush"]);
+}
+
+function listener_connection(cb)
+{
+ const conn = {
+ port: browser.runtime.connect({name: CONNECTION_TYPE.BROADCAST_LISTEN})
+ };
+
+ conn.port.onMessage.addListener(cb);
+
+ return conn;
+}
+
+function subscribe(listener_conn, channel_name)
+{
+ listener_conn.port.postMessage(["subscribe", channel_name]);
+}
+
+function unsubscribe(listener_conn, channel_name)
+{
+ listener_conn.port.postMessage(["unsubscribe", channel_name]);
+}
+
+function close(conn)
+{
+ conn.port.disconnect();
+}
+
+const broadcast = {
+ sender_connection, out, prepare, discard, flush,
+ listener_connection, subscribe, unsubscribe,
+ close
+};
+
+/*
+ * EXPORTS_START
+ * EXPORT broadcast
+ * EXPORTS_END
+ */
diff --git a/common/connection_types.js b/common/connection_types.js
index a571cb9..9747e5c 100644
--- a/common/connection_types.js
+++ b/common/connection_types.js
@@ -49,7 +49,9 @@
const CONNECTION_TYPE = {
REMOTE_STORAGE : "0",
PAGE_ACTIONS : "1",
- ACTIVITY_INFO : "2"
+ ACTIVITY_INFO : "2",
+ BROADCAST_SEND: "3",
+ BROADCAST_LISTEN: "4"
};
/*
diff --git a/copyright b/copyright
index 9c39134..a2e09d1 100644
--- a/copyright
+++ b/copyright
@@ -9,7 +9,7 @@ Comment: Wojtek Kosior promises not to sue even in case of violations
of the license.
Files: *.sh default_settings.json Makefile.in compute_scripts.awk
- CHROMIUM_exports_init.js
+ CHROMIUM_exports_init.js pytest.ini
Copyright: 2021 Wojtek Kosior <koszko@koszko.org>
License: CC0
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000..6c8afcc
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,19 @@
+#!/usr/bin/env pytest
+
+# This file is part of Haketilo
+#
+# Copyright (C) 2021, Wojtek Kosior
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the CC0 1.0 Universal License as published by
+# the Creative Commons Corporation.
+#
+# 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
+# CC0 1.0 Universal License for more details.
+
+[pytest]
+markers =
+ ext_data: define a custom testing extension for `webextension` fixture.
+ get_page: define a url the `driver` fixture should navigate the browser to.
diff --git a/test/extension_crafting.py b/test/extension_crafting.py
index 6f1800b..9b985b3 100644
--- a/test/extension_crafting.py
+++ b/test/extension_crafting.py
@@ -116,6 +116,8 @@ def make_extension(destination_dir,
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)
return destination_path
diff --git a/test/unit/conftest.py b/test/unit/conftest.py
index e1c98a1..eec311c 100644
--- a/test/unit/conftest.py
+++ b/test/unit/conftest.py
@@ -27,6 +27,7 @@ Common fixtures for Haketilo unit tests
import pytest
from pathlib import Path
+from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@@ -42,11 +43,24 @@ def proxy():
httpd.shutdown()
@pytest.fixture(scope="package")
-def driver(proxy):
+def _driver(proxy):
with firefox_safe_mode() as driver:
yield driver
driver.quit()
+def close_all_but_one_window(driver):
+ while len(driver.window_handles) > 1:
+ driver.switch_to.window(driver.window_handles[-1])
+ driver.close()
+ driver.switch_to.window(driver.window_handles[0])
+
+@pytest.fixture()
+def driver(_driver, request):
+ nav_target = request.node.get_closest_marker('get_page')
+ close_all_but_one_window(_driver)
+ _driver.get(nav_target.args[0] if nav_target else 'about:blank')
+ yield _driver
+
@pytest.fixture()
def webextension(driver, request):
ext_data = request.node.get_closest_marker('ext_data')
@@ -58,7 +72,7 @@ def webextension(driver, request):
driver.get('https://gotmyowndoma.in/')
addon_id = driver.install_addon(str(ext_path), temporary=True)
WebDriverWait(driver, 10).until(
- EC.title_contains("Extension's options page for testing")
+ EC.url_matches('^moz-extension://.*')
)
yield
driver.uninstall_addon(addon_id)
@@ -115,22 +129,28 @@ def _execute_in_page_context(driver, script, args):
raise e from None
-@pytest.fixture(scope="package")
-def execute_in_page(driver):
- def do_execute(script, *args, **kwargs):
- if 'page' in kwargs:
- driver.get(kwargs['page'])
+# Some fixtures here just define functions that operate on driver. We should
+# consider making them into webdriver wrapper class methods.
+@pytest.fixture()
+def execute_in_page(driver):
+ def do_execute(script, *args):
return _execute_in_page_context(driver, script, args)
yield do_execute
-@pytest.fixture(scope="package")
+@pytest.fixture()
def load_into_page(driver):
- def do_load(path, import_dirs, *args, **kwargs):
- if 'page' in kwargs:
- driver.get(kwargs['page'])
-
+ def do_load(path, import_dirs, *args):
_execute_in_page_context(driver, load_script(path, import_dirs), args)
yield do_load
+
+@pytest.fixture()
+def wait_elem_text(driver):
+ def do_wait(id, text):
+ WebDriverWait(driver, 10).until(
+ EC.text_to_be_present_in_element((By.ID, id), text)
+ )
+
+ yield do_wait
diff --git a/test/unit/test_basic.py b/test/unit/test_basic.py
index 3b09cb6..ca956e7 100644
--- a/test/unit/test_basic.py
+++ b/test/unit/test_basic.py
@@ -31,18 +31,19 @@ def test_driver(driver):
)
assert "Schrodinger's Document" in title
+@pytest.mark.get_page('https://gotmyowndoma.in')
def test_script_loader(execute_in_page, load_into_page):
"""
A trivial test case that verifies Haketilo's .js files can be properly
loaded into a test page together with their dependencies.
"""
- load_into_page('common/stored_types.js', ['common'],
- page='https://gotmyowndoma.in')
+ load_into_page('common/stored_types.js', ['common'])
assert execute_in_page('returnval(TYPE_PREFIX.VAR);') == '_'
@pytest.mark.ext_data({})
-def test_webextension(driver, webextension):
+@pytest.mark.usefixtures('webextension')
+def test_webextension(driver):
"""
A trivial test case that verifies a test WebExtension created and installed
by the `webextension` fixture works and redirects specially-constructed URLs
diff --git a/test/unit/test_broadcast.py b/test/unit/test_broadcast.py
new file mode 100644
index 0000000..c8c19d1
--- /dev/null
+++ b/test/unit/test_broadcast.py
@@ -0,0 +1,181 @@
+# SPDX-License-Identifier: CC0-1.0
+
+"""
+Haketilo unit tests - message broadcasting
+"""
+
+# This file is part of Haketilo
+#
+# Copyright (C) 2021, Wojtek Kosior
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the CC0 1.0 Universal License as published by
+# the Creative Commons Corporation.
+#
+# 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
+# CC0 1.0 Universal License for more details.
+
+import pytest
+
+from ..script_loader import load_script
+
+def broker_js():
+ return load_script('background/broadcast_broker.js',
+ ['common', 'background']) + ';start_broadcast_broker();'
+
+def broadcast_js():
+ return load_script('common/broadcast.js', ['common'])
+
+test_page_html = '''
+<!DOCTYPE html>
+<script src="/testpage.js"></script>
+<h2>d0 (channel `somebodyoncetoldme`)</h2>
+<div id="d0"></div>
+<h2>d1 (channel `worldisgonnarollme`)</h2>
+<div id="d1"></div>
+<h2>d2 (both channels)</h2>
+<div id="d2"></div>
+'''
+
+@pytest.mark.ext_data({
+ 'background_script': broker_js,
+ 'test_page': test_page_html,
+ 'extra_files': {
+ 'testpage.js': broadcast_js
+ }
+})
+@pytest.mark.usefixtures('webextension')
+def test_broadcast(driver, execute_in_page, wait_elem_text):
+ """
+ A test that verifies the broadcasting system based on WebExtension messaging
+ API and implemented in `background/broadcast_broker.js` and
+ `common/broadcast.js` works correctly.
+ """
+
+ # The broadcast facility is meant to enable message distribution between
+ # multiple contexts (e.g. different tabs/windows). Let's open the same
+ # extension's test page in a second window.
+ driver.execute_script(
+ '''
+ window.open(window.location.href, "_blank");
+ window.open(window.location.href, "_blank");
+ ''')
+ windows = [*driver.window_handles]
+ assert len(windows) == 3
+
+ # Let's first test if a simple message can be successfully broadcasted
+ driver.switch_to.window(windows[0])
+ execute_in_page(
+ '''
+ const divs = [0, 1, 2].map(n => document.getElementById("d" + n));
+ let appender = n => (t => divs[n].append("\\n" + `[${t[0]}, ${t[1]}]`));
+ let listener0 = broadcast.listener_connection(appender(0));
+ broadcast.subscribe(listener0, "somebodyoncetoldme");
+ ''')
+
+ driver.switch_to.window(windows[1])
+ execute_in_page(
+ '''
+ let sender0 = broadcast.sender_connection();
+ broadcast.out(sender0, "somebodyoncetoldme", "iaintthesharpesttool");
+ ''')
+
+ driver.switch_to.window(windows[0])
+ wait_elem_text('d0', '[somebodyoncetoldme, iaintthesharpesttool]')
+
+ # Let's add 2 more listeners
+ driver.switch_to.window(windows[0])
+ execute_in_page(
+ '''
+ let listener1 = broadcast.listener_connection(appender(1));
+ broadcast.subscribe(listener1, "worldisgonnarollme");
+ let listener2 = broadcast.listener_connection(appender(2));
+ broadcast.subscribe(listener2, "worldisgonnarollme");
+ broadcast.subscribe(listener2, "somebodyoncetoldme");
+ ''')
+
+ # Let's send one message to one channel and one to the other. Verify they
+ # were received by the rght listeners.
+ driver.switch_to.window(windows[1])
+ execute_in_page(
+ '''
+ broadcast.out(sender0, "somebodyoncetoldme", "intheshed");
+ broadcast.out(sender0, "worldisgonnarollme", "shewaslooking");
+ ''')
+
+ driver.switch_to.window(windows[0])
+ wait_elem_text('d0', 'intheshed')
+ wait_elem_text('d1', 'shewaslooking')
+ wait_elem_text('d2', 'intheshed')
+ wait_elem_text('d2', 'shewaslooking')
+
+ text = execute_in_page('returnval(divs[0].innerText);')
+ assert 'shewaslooking' not in text
+ text = execute_in_page('returnval(divs[1].innerText);')
+ assert 'intheshed' not in text
+
+ # Let's create a second sender in third window and use it to send messages
+ # with the 'prepare' feature.
+ driver.switch_to.window(windows[2])
+ execute_in_page(
+ '''
+ let sender1 = broadcast.sender_connection();
+ broadcast.prepare(sender1, "somebodyoncetoldme", "kindadumb");
+ broadcast.out(sender1, "worldisgonnarollme", "withherfinger");
+ ''')
+
+ driver.switch_to.window(windows[0])
+ wait_elem_text('d1', 'withherfinger')
+ text = execute_in_page('returnval(divs[0].innerText);')
+ assert 'kindadumb' not in text
+
+ driver.switch_to.window(windows[2])
+ execute_in_page('broadcast.flush(sender1);')
+
+ driver.switch_to.window(windows[0])
+ wait_elem_text('d0', 'kindadumb')
+
+ # Let's verify that prepare()'d messages are properly discarded when
+ # discard() is called.
+ driver.switch_to.window(windows[2])
+ execute_in_page(
+ '''
+ broadcast.prepare(sender1, "somebodyoncetoldme", "andherthumb");
+ broadcast.discard(sender1);
+ broadcast.prepare(sender1, "somebodyoncetoldme", "andhermiddlefinger");
+ broadcast.flush(sender1);
+ ''')
+
+ driver.switch_to.window(windows[0])
+ wait_elem_text('d0', 'andhermiddlefinger')
+ text = execute_in_page('returnval(divs[0].innerText);')
+ assert 'andherthumb' not in text
+
+ # Let's verify prepare()'d messages are properly auto-flushed when the other
+ # end of the connection gets killed (e.g. because browser tab gets closed).
+ driver.switch_to.window(windows[2])
+ execute_in_page(
+ '''
+ broadcast.prepare(sender1, "worldisgonnarollme", "intheshape", 500);
+ ''')
+ driver.close()
+
+ driver.switch_to.window(windows[0])
+ wait_elem_text('d2', 'intheshape')
+
+ # Verify listener's connection gets closed properly.
+ execute_in_page('broadcast.close(listener0); broadcast.close(listener1);')
+
+ driver.switch_to.window(windows[1])
+ execute_in_page('broadcast.out(sender0, "worldisgonnarollme", "ofanL");')
+ execute_in_page('broadcast.out(sender0, "somebodyoncetoldme", "forehead");')
+
+ driver.switch_to.window(windows[0])
+ wait_elem_text('d2', 'ofanL')
+ wait_elem_text('d2', 'forehead')
+ for i in (0, 1):
+ text = execute_in_page('returnval(divs[arguments[0]].innerText);', i)
+ assert 'ofanL' not in text
+ assert 'forehead' not in text
diff --git a/test/unit/test_indexeddb.py b/test/unit/test_indexeddb.py
index f1322fb..965f318 100644
--- a/test/unit/test_indexeddb.py
+++ b/test/unit/test_indexeddb.py
@@ -47,12 +47,13 @@ sample_files_by_hash = dict([[file['hash_key'], file['contents']]
def file_ref(file_name):
return {'file': file_name, 'hash_key': sample_files[file_name]['hash_key']}
+@pytest.mark.get_page('https://gotmyowndoma.in')
def test_save_remove_item(execute_in_page, indexeddb_code):
"""
indexeddb.js facilitates operating on Haketilo's internal database.
Verify database operations work properly.
"""
- execute_in_page(indexeddb_code, page='https://gotmyowndoma.in')
+ execute_in_page(indexeddb_code)
# Don't use Haketilo's default initial data.
execute_in_page(
'''{
diff --git a/test/unit/test_patterns.py b/test/unit/test_patterns.py
index 802bf4e..99e1ed5 100644
--- a/test/unit/test_patterns.py
+++ b/test/unit/test_patterns.py
@@ -25,12 +25,13 @@ from ..script_loader import load_script
def patterns_code():
yield load_script('common/patterns.js', ['common'])
+@pytest.mark.get_page('https://gotmyowndoma.in')
def test_regexes(execute_in_page, patterns_code):
"""
patterns.js contains regexes used for URL parsing.
Verify they work properly.
"""
- execute_in_page(patterns_code, page='https://gotmyowndoma.in')
+ execute_in_page(patterns_code)
valid_url = 'https://example.com/a/b?ver=1.2.3#heading2'
valid_url_rest = 'example.com/a/b?ver=1.2.3#heading2'
@@ -90,12 +91,13 @@ def test_regexes(execute_in_page, patterns_code):
'@bad.url/')
assert match is None
+@pytest.mark.get_page('https://gotmyowndoma.in')
def test_deconstruct_url(execute_in_page, patterns_code):
"""
patterns.js contains deconstruct_url() function that handles URL parsing.
Verify it works properly.
"""
- execute_in_page(patterns_code, page='https://gotmyowndoma.in')
+ execute_in_page(patterns_code)
deco = execute_in_page('returnval(deconstruct_url(arguments[0]));',
'https://eXaMpLe.com/a/b?ver=1.2.3#heading2')
diff --git a/test/unit/test_patterns_query_tree.py b/test/unit/test_patterns_query_tree.py
index e282592..a67e22f 100644
--- a/test/unit/test_patterns_query_tree.py
+++ b/test/unit/test_patterns_query_tree.py
@@ -25,13 +25,14 @@ from ..script_loader import load_script
def patterns_tree_code():
yield load_script('common/patterns_query_tree.js', ['common'])
+@pytest.mark.get_page('https://gotmyowndoma.in')
def test_modify_branch(execute_in_page, patterns_tree_code):
"""
patterns_query_tree.js contains Pattern Tree data structure that allows
arrays of string labels to be mapped to items.
Verify operations modifying a single branch of such tree work properly.
"""
- execute_in_page(patterns_tree_code, page='https://gotmyowndoma.in')
+ execute_in_page(patterns_tree_code)
execute_in_page(
'''
let items_added;
@@ -195,13 +196,14 @@ def test_modify_branch(execute_in_page, patterns_tree_code):
}
}
+@pytest.mark.get_page('https://gotmyowndoma.in')
def test_search_branch(execute_in_page, patterns_tree_code):
"""
patterns_query_tree.js contains Pattern Tree data structure that allows
arrays of string labels to be mapped to items.
Verify searching a single branch of such tree work properly.
"""
- execute_in_page(patterns_tree_code, page='https://gotmyowndoma.in')
+ execute_in_page(patterns_tree_code)
execute_in_page(
'''
const item_adder = item => (array => [...(array || []), item]);
@@ -282,13 +284,14 @@ def test_search_branch(execute_in_page, patterns_tree_code):
'\nresult:', result, file=sys.stderr)
raise e from None
+@pytest.mark.get_page('https://gotmyowndoma.in')
def test_pattern_tree(execute_in_page, patterns_tree_code):
"""
patterns_query_tree.js contains Pattern Tree data structure that allows
arrays of string labels to be mapped to items.
Verify operations on entire such tree work properly.
"""
- execute_in_page(patterns_tree_code, page='https://gotmyowndoma.in')
+ execute_in_page(patterns_tree_code)
# Perform tests with all possible patterns for a simple URL.
url = 'https://example.com'