diff options
author | Wojtek Kosior <koszko@koszko.org> | 2022-01-31 18:06:13 +0100 |
---|---|---|
committer | Wojtek Kosior <koszko@koszko.org> | 2022-01-31 18:06:13 +0100 |
commit | ad69f9c86b950cc84ca103e65824b9c9129d3999 (patch) | |
tree | a4314c8a5031b9fb2a278021b2388b86190c2823 | |
parent | 4c6a2323d90e9321ec2b78e226167b3013ea69ab (diff) | |
download | browser-extension-ad69f9c86b950cc84ca103e65824b9c9129d3999.tar.gz browser-extension-ad69f9c86b950cc84ca103e65824b9c9129d3999.zip |
add support for testing with other browsers (especially Abrowser and Librewolf)
There are still some spurious failures when running under those newer browsers. Those will be systematically investigated and fixed.
-rw-r--r-- | Makefile.in | 3 | ||||
-rw-r--r-- | common/broadcast.js | 4 | ||||
-rw-r--r-- | common/entities.js | 4 | ||||
-rw-r--r-- | common/indexeddb.js | 2 | ||||
-rw-r--r-- | common/patterns_query_tree.js | 2 | ||||
-rwxr-xr-x | configure | 73 | ||||
-rw-r--r-- | html/file_preview.html | 48 | ||||
-rw-r--r-- | html/file_preview.js | 75 | ||||
-rw-r--r-- | html/item_list.js | 7 | ||||
-rw-r--r-- | html/item_preview.js | 46 | ||||
-rw-r--r-- | manifest.json | 2 | ||||
-rw-r--r-- | test/data/pages/scripts_to_block_1.html | 2 | ||||
-rw-r--r-- | test/misc_constants.py | 12 | ||||
-rwxr-xr-x | test/profiles.py | 6 | ||||
-rw-r--r-- | test/unit/test_indexeddb.py | 23 | ||||
-rw-r--r-- | test/unit/test_item_list.py | 34 | ||||
-rw-r--r-- | test/unit/test_item_preview.py | 51 | ||||
-rw-r--r-- | test/unit/test_patterns_query_manager.py | 29 | ||||
-rw-r--r-- | test/unit/test_popup.py | 32 |
19 files changed, 315 insertions, 140 deletions
diff --git a/Makefile.in b/Makefile.in index 570260b..76aaf8e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -16,6 +16,7 @@ SHELL = /bin/sh VPATH = <<VPATH>> version = <<VERSION>> +PYTEST = <<PYTEST>> extension_files = background/ common/ content/ html/ licenses/ \ copyright default_settings.json manifest.json @@ -70,7 +71,7 @@ test/certs/rootCA.pem: test/certs/rootCA.key -subj "/CN=Haketilo Test" test: test/certs/rootCA.pem test/certs/site.key $(default_target)-build.zip - MOZ_HEADLESS=whatever pytest + MOZ_HEADLESS=whatever $(PYTEST) test-environment: test/certs/rootCA.pem test/certs/site.key python3 -m test diff --git a/common/broadcast.js b/common/broadcast.js index 4dcac2b..4fc5237 100644 --- a/common/broadcast.js +++ b/common/broadcast.js @@ -46,6 +46,7 @@ function sender_connection() { return { + type: "sender", port: connect_to_background("broadcast_send") }; } @@ -92,6 +93,7 @@ function flush(sender_conn) function listener_connection(cb) { const conn = { + type: "listener", port: connect_to_background("broadcast_listen") }; @@ -115,6 +117,8 @@ function unsubscribe(listener_conn, channel_name) function close(conn) { + if (conn.type === "sender") + flush(conn); conn.port.disconnect(); } #EXPORT close diff --git a/common/entities.js b/common/entities.js index 3ccbf04..96de5cb 100644 --- a/common/entities.js +++ b/common/entities.js @@ -74,7 +74,7 @@ function item_id_string(...args) { #EXPORT item_id_string /* vers should be an array of comparable values. Return the greatest one. */ -const max = vals => Array.reduce(vals, (v1, v2) => v1 > v2 ? v1 : v2); +const max = vals => vals.reduce((v1, v2) => v1 > v2 ? v1 : v2); /* * versioned_item should be a dict with keys being version strings and values @@ -167,6 +167,6 @@ const version_reductor = (acc, n) => [...(n || acc.length ? [n] : []), ...acc]; * * Returns a *new* array. Doesn't modify its argument. */ -const normalize_version = ver => Array.reduceRight(ver, version_reductor, []); +const normalize_version = ver => ver.reduceRight(version_reductor, []); #ENDIF diff --git a/common/indexeddb.js b/common/indexeddb.js index 271dfce..f916162 100644 --- a/common/indexeddb.js +++ b/common/indexeddb.js @@ -56,7 +56,7 @@ let initial_data = ( const db_version = [1, 0, 0]; const nr_reductor = ([i, s], num) => [i - 1, s + num * 1024 ** i]; -const version_nr = ver => Array.reduce(ver.slice(0, 3), nr_reductor, [2, 0])[1]; +const version_nr = ver => ver.slice(0, 3).reduce(nr_reductor, [2, 0])[1]; const stores = [ ["files", {keyPath: "hash_key"}], diff --git a/common/patterns_query_tree.js b/common/patterns_query_tree.js index ec1d989..ea3607e 100644 --- a/common/patterns_query_tree.js +++ b/common/patterns_query_tree.js @@ -68,7 +68,7 @@ function is_empty_node(tree_node) { return false; } - if (Array.reduce(tree_node.wildcard_matches, (a, b) => b && a !== null, 1)) + if (tree_node.wildcard_matches.reduce((a, b) => b && a !== null, 1)) return false; return tree_node.literal_match === null; @@ -18,16 +18,29 @@ set -e BROWSERPATH='' SRCDIR='' TARGET='' +BROWSER_BINARY='' +CLEAN_PROFILE='' +DRIVER='' +PYTEST='' # Parse command line options while [ "x$1" != x ]; do case "$1" in - --srcdir=*) SRCDIR="$(echo "$1" | cut -c 10-)";; - --srcdir) SRCDIR="$2"; shift;; - "DESTDIR"=*) DESTDIR="$(echo "$1" | cut -c 9-)";; - "UPDATE_URL"=*) UPDATE_URL="$(echo "$1" | cut -c 12-)";; - --host=*) TARGET="$(echo "$1" | cut -c 8-)";; - --host) TARGET="$2"; shift;; + --srcdir=*) SRCDIR="$(printf %s "$1" | cut -c 10-)";; + --srcdir) SRCDIR="$2"; shift;; + --browser-binary=*) BROWSER_BINARY="$(printf %s "$1" | cut -c 18-)";; + --browser-binary) BROWSER_BINARY="$2"; shift;; + --clean-profile=*) CLEAN_PROFILE="$(printf %s "$1" | cut -c 17-)";; + --clean-profile) CLEAN_PROFILE="$2"; shift;; + --driver=*) DRIVER="$(printf %s "$1" | cut -c 10-)";; + --driver) DRIVER="$2"; shift;; + --pytest=*) PYTEST="$(printf %s "$1" | cut -c 10-)";; + --pytest) PYTEST="$2"; shift;; + --srcdir) SRCDIR="$2"; shift;; + "DESTDIR"=*) DESTDIR="$(printf %s "$1" | cut -c 9-)";; + "UPDATE_URL"=*) UPDATE_URL="$(printf %s "$1" | cut -c 12-)";; + --host=*) TARGET="$(printf %s "$1" | cut -c 8-)";; + --host) TARGET="$2"; shift;; # browsers chromium | chrome | google-chrome | mozilla |\ @@ -70,6 +83,20 @@ else BROWSERPATH="$(realpath "$(which $TARGET)")" fi +# Autodetect browser binary (needed for Selenium) +if [ "x$BROWSER_BINARY" = x ]; then + if [ "x$TARGET" = xabrowser ]; then + # Trisquel's path to Abrowser + BROWSER_BINARY=/usr/lib/abrowser/abrowser + if [ "x$TARGET" = xabrowser ]; then + # Debian's path to Librewolf + BROWSER_BINARY=/usr/share/librewolf/librewolf + elif [ "x$TARGET" = xicecat ]; then + # Parabola's path to IceCat + BROWSER_BINARY=/usr/lib/icecat/icecat + fi +fi + # Check and standardize target case "$TARGET" in mozilla | firefox | abrowser | icecat | iceweasel-uxp |\ @@ -79,6 +106,27 @@ case "$TARGET" in *) echo Invalid target "'$TARGET'" >&2; exit 2;; esac +# Autodetect Selenium driver +if [ "x$DRIVER" = x ]; then + if [ "x$TARGET" = mozilla ]; then + DRIVER=geckodriver + fi +fi + +# Autodetect clean profile directory for use in selenium tests +if [ "x$CLEAN_PROFILE" = x ]; then + if [ "x$TARGET" = mozilla ]; then + CLEAN_PROFILE="$SRCDIR"/test/default_profile/icecat_empty + fi +fi + +# Autodetect pytest +for PYTEST_GUESS in pytest pytest-3 pytest3; do + if [ "x$PYTEST" = x ]; then + PYTEST="$(which $PYTEST_GUESS || true)" + fi +done + # Autodetect DESTDIR (no check needed) if [ "x$DESTDIR" = x ]; then echo Guessing installation directory. @@ -95,11 +143,14 @@ if [ "x$DESTDIR" = x ]; then fi # Write record.conf (LEAVE SRCDIR FIRST) -echo srcdir = "$SRCDIR" > record.conf -echo default_target = "$TARGET" >> record.conf -echo DESTDIR = "$DESTDIR" >> record.conf -echo UPDATE_URL = "$UPDATE_URL" >> record.conf - +printf '%s\n' "srcdir = $SRCDIR" > record.conf +printf '%s\n' "default_target = $TARGET" >> record.conf +printf '%s\n' "DESTDIR = $DESTDIR" >> record.conf +printf '%s\n' "UPDATE_URL = $UPDATE_URL" >> record.conf +printf '%s\n' "DRIVER = $DRIVER" >> record.conf +printf '%s\n' "BROWSER_BINARY = $BROWSER_BINARY" >> record.conf +printf '%s\n' "CLEAN_PROFILE = $CLEAN_PROFILE" >> record.conf +printf '%s\n' "PYTEST = $PYTEST" >> record.conf # Prepare and run write_makefile.sh (as config.status) if [ ! -e config.status ]; then diff --git a/html/file_preview.html b/html/file_preview.html new file mode 100644 index 0000000..20e2392 --- /dev/null +++ b/html/file_preview.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<!-- + SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0 + + Haketilo's preview of a file kept in IndexedDB + + This file is part of Haketilo. + + Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org> + + File is dual-licensed. You can choose either GPLv3+, CC BY-SA or both. + + 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 + licenses. 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. + --> +<html> + <head> + <meta charset="utf-8"/> + <title>File preview</title> +#LOADCSS html/reset.css +#LOADCSS html/base.css + <style> + .error_msg { + color: #a33; + /* also italics maybe?*/ + } + </style> + </head> + <body> + <div id="info_msg">loading...</div> + <div id="error_msg" class="hide"></div> +#LOADJS html/file_preview.js + </body> +</html> diff --git a/html/file_preview.js b/html/file_preview.js new file mode 100644 index 0000000..3e22225 --- /dev/null +++ b/html/file_preview.js @@ -0,0 +1,75 @@ +/** + * This file is part of Haketilo. + * + * Function: Haketilo's preview of a file kept in IndexedDB. + * + * Copyright (C) 2022 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 of this code in a + * proprietary program, I am not going to enforce this in court. + */ + +#IMPORT common/indexeddb.js AS haketilodb + +#FROM html/DOM_helpers.js IMPORT by_id + +const error_msg = by_id("error_msg"), info_msg = by_id("info_msg"); + +function error(msg) { + info_msg.remove(); + error_msg.classList.remove("hide"); + error_msg.innerText = msg; +} + +async function show_preview(hash_key) { + const db = await haketilodb.get(); + const file = await haketilodb.idb_get(db.transaction("files"), + "files", hash_key); + if (file === undefined) { + error("Couldn't find file in Haketilo's internal database :("); + } else { + const blobby_opts = {type: "text/plain;charset=UTF-8"}; + const blobby = new Blob([file.contents], blobby_opts); + location.replace(URL.createObjectURL(blobby)); + } +} + +const match = /^[^#]*#(.*)$/.exec(document.URL); +if (!match) { + error("Bad URL :("); +} else { + const hash_key = match[1]; + show_preview(hash_key); +} diff --git a/html/item_list.js b/html/item_list.js index a616713..bc1acb9 100644 --- a/html/item_list.js +++ b/html/item_list.js @@ -52,11 +52,8 @@ function preview_item(list_ctx, item, ignore_dialog=false) if (list_ctx.dialog_ctx.shown && !ignore_dialog) return; - list_ctx.preview_ctx = list_ctx.preview_cb( - item.definition, - list_ctx.preview_ctx, - list_ctx.dialog_ctx - ); + list_ctx.preview_ctx = + list_ctx.preview_cb(item.definition, list_ctx.preview_ctx); list_ctx.preview_container.prepend(list_ctx.preview_ctx.main_div); if (list_ctx.previewed_item !== null) diff --git a/html/item_preview.js b/html/item_preview.js index dccf2d4..c55183c 100644 --- a/html/item_preview.js +++ b/html/item_preview.js @@ -44,10 +44,10 @@ #IMPORT common/indexeddb.js AS haketilodb #IMPORT html/dialog.js +#FROM common/browser.js IMPORT browser #FROM html/DOM_helpers.js IMPORT clone_template -function populate_list(ul, items) -{ +function populate_list(ul, items) { for (const item of items) { const li = document.createElement("li"); li.append(item); @@ -55,41 +55,21 @@ function populate_list(ul, items) } } -/* Link click handler used in make_file_link(). */ -async function file_link_clicked(preview_object, file_ref, event) -{ - event.preventDefault(); - - const db = await haketilodb.get(); - const file = await haketilodb.idb_get(db.transaction("files"), - "files", file_ref.hash_key); - if (file === undefined) { - dialog.error(preview_object.dialog_context, - "File missing from Haketilo's internal database :("); - } else { - const encoded_file = encodeURIComponent(file.contents); - open(`data:text/plain;charset=utf8,${encoded_file}`, '_blank'); - } -} +const file_preview_link = browser.runtime.getURL("html/file_preview.html"); /* * The default function to use to create file preview link. Links it creates can * be used to view files from IndexedDB. */ -function make_file_link(preview_object, file_ref) -{ +function make_file_link(preview_object, file_ref) { const a = document.createElement("a"); - a.href = "javascript:void(0)"; + a.href = `${file_preview_link}#${file_ref.hash_key}`; a.innerText = file_ref.file; - a.addEventListener("click", - e => file_link_clicked(preview_object, file_ref, e)); - + a.target = "_blank"; return a; } -function resource_preview(resource, preview_object, dialog_context, - make_link_cb=make_file_link) -{ +function resource_preview(resource, preview_object, link_cb=make_file_link) { if (preview_object === undefined) preview_object = clone_template("resource_preview"); @@ -104,7 +84,7 @@ function resource_preview(resource, preview_object, dialog_context, [...preview_object.dependencies.childNodes].forEach(n => n.remove()); populate_list(preview_object.dependencies, resource.dependencies); - const link_maker = file_ref => make_link_cb(preview_object, file_ref); + const link_maker = file_ref => link_cb(preview_object, file_ref); [...preview_object.scripts.childNodes].forEach(n => n.remove()); populate_list(preview_object.scripts, resource.scripts.map(link_maker)); @@ -113,15 +93,11 @@ function resource_preview(resource, preview_object, dialog_context, populate_list(preview_object.copyright, resource.source_copyright.map(link_maker)); - preview_object.dialog_context = dialog_context; - return preview_object; } #EXPORT resource_preview -function mapping_preview(mapping, preview_object, dialog_context, - make_link_cb=make_file_link) -{ +function mapping_preview(mapping, preview_object, link_cb=make_file_link) { if (preview_object === undefined) preview_object = clone_template("mapping_preview"); @@ -145,14 +121,12 @@ function mapping_preview(mapping, preview_object, dialog_context, } } - const link_maker = file_ref => make_link_cb(preview_object, file_ref); + const link_maker = file_ref => link_cb(preview_object, file_ref); [...preview_object.copyright.childNodes].forEach(n => n.remove()); populate_list(preview_object.copyright, mapping.source_copyright.map(link_maker)); - preview_object.dialog_context = dialog_context; - return preview_object; } #EXPORT mapping_preview diff --git a/manifest.json b/manifest.json index 25b5a1a..350c39a 100644 --- a/manifest.json +++ b/manifest.json @@ -68,6 +68,8 @@ "16": "icons/haketilo16.png" }, "default_title": "Haketilo", + // Both popup.html and settings.html depend on file_preview.html. +#LOADHTML html/file_preview.html #LOADHTML html/popup.html "default_popup": "html/popup.html" }, diff --git a/test/data/pages/scripts_to_block_1.html b/test/data/pages/scripts_to_block_1.html index 1aa49ee..164979d 100644 --- a/test/data/pages/scripts_to_block_1.html +++ b/test/data/pages/scripts_to_block_1.html @@ -37,7 +37,7 @@ href="javascript:window.__run = [...(window.__run || []), 'href'];void(0);"> Click Meee! </a> - <iframe src="javascript:window.parent.__run = [...(window.parent.__run || []), 'src'];"> + <iframe src="javascript:void(window.parent.__run = [...(window.parent.__run || []), 'src']);"> </iframe> <object data="javascript:window.__run = [...(window.__run || []), 'data'];"> </object> diff --git a/test/misc_constants.py b/test/misc_constants.py index db9f3f1..51602b3 100644 --- a/test/misc_constants.py +++ b/test/misc_constants.py @@ -27,6 +27,7 @@ Miscellaneous data that were found useful # 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 re from pathlib import Path here = Path(__file__).resolve().parent @@ -36,10 +37,13 @@ awk_script_name = 'compute_scripts.awk' unit_test_defines = ['-D', 'MOZILLA', '-D', 'MV2', '-D', 'TEST', '-D', 'UNIT_TEST', '-D', 'DEBUG'] -default_firefox_binary = '/usr/lib/icecat/icecat' -# The browser might be loading some globally-installed add-ons by default. They -# could interfere with the tests, so we'll disable all of them. -default_clean_profile_dir = here / 'default_profile' / 'icecat_empty' +conf_line_regex = re.compile(r'^([^=]+)=(.*)$') +conf_settings = {} +with open(here.parent / 'record.conf', 'rt') as conf: + for line in conf.readlines(): + match = conf_line_regex.match(line) + if match: + conf_settings[match.group(1).strip()] = match.group(2).strip() default_proxy_host = '127.0.0.1' default_proxy_port = 1337 diff --git a/test/profiles.py b/test/profiles.py index 4892894..ae997fc 100755 --- a/test/profiles.py +++ b/test/profiles.py @@ -80,7 +80,7 @@ def set_webextension_uuid(profile, extension_id, uuid=default_extension_uuid): profile.set_preference('extensions.webextensions.uuids', json.dumps({extension_id: uuid})) -def firefox_safe_mode(firefox_binary=default_firefox_binary, +def firefox_safe_mode(firefox_binary=conf_settings['BROWSER_BINARY'], proxy_host=default_proxy_host, proxy_port=default_proxy_port): """ @@ -97,8 +97,8 @@ def firefox_safe_mode(firefox_binary=default_firefox_binary, return HaketiloFirefox(options=options, firefox_profile=profile, firefox_binary=firefox_binary) -def firefox_with_profile(firefox_binary=default_firefox_binary, - profile_dir=default_clean_profile_dir, +def firefox_with_profile(firefox_binary=conf_settings['BROWSER_BINARY'], + profile_dir=conf_settings['CLEAN_PROFILE'], proxy_host=default_proxy_host, proxy_port=default_proxy_port): """ diff --git a/test/unit/test_indexeddb.py b/test/unit/test_indexeddb.py index 550b923..7ce4781 100644 --- a/test/unit/test_indexeddb.py +++ b/test/unit/test_indexeddb.py @@ -323,17 +323,18 @@ def test_haketilodb_track(driver, execute_in_page, wait_elem_text): # Create elements that will have tracked data inserted under them. driver.switch_to.window(windows[0]) - execute_in_page(''' - for (const store_name of trackable) { - const h2 = document.createElement("h2"); - h2.innerText = store_name; - document.body.append(h2); - - const ul = document.createElement("ul"); - ul.id = store_name; - document.body.append(ul); - } - ''') + execute_in_page( + ''' + for (const store_name of trackable) { + const h2 = document.createElement("h2"); + h2.innerText = store_name; + document.body.append(h2); + + const ul = document.createElement("ul"); + ul.id = store_name; + document.body.append(ul); + } + ''') # Mock initial_data. sample_resource = make_sample_resource() diff --git a/test/unit/test_item_list.py b/test/unit/test_item_list.py index ff532f8..0702129 100644 --- a/test/unit/test_item_list.py +++ b/test/unit/test_item_list.py @@ -217,17 +217,6 @@ def test_item_list_displaying(driver, execute_in_page, item_type): assert f'item{i}' in text assert f'Item {i}' in text - # Check that file preview link works. - window0 = driver.window_handles[0] - driver.find_element_by_link_text('report.spdx').click() - WebDriverWait(driver, 10).until(lambda _: len(driver.window_handles) == 2) - window1 = next(filter(lambda w: w != window0, driver.window_handles)) - driver.switch_to.window(window1) - assert 'dummy report' in driver.page_source - - driver.close() - driver.switch_to.window(window0) - # Check that item removal confirmation dialog is displayed correctly. execute_in_page('list_ctx.remove_but.click();') WebDriverWait(driver, 10).until(lambda _: dialog_container.is_displayed()) @@ -271,29 +260,6 @@ def test_item_list_displaying(driver, execute_in_page, item_type): execute_in_page('list_ctx.ul.children[1].click();') - # Check that missing file causes the right error dialog to appear. - execute_in_page( - '''{ - async function steal_file(hash_key) - { - const db = await haketilodb.get(); - const transaction = db.transaction("files", "readwrite"); - transaction.objectStore("files").delete(hash_key); - } - returnval(steal_file(arguments[0])); - }''', - sample_files['LICENSES/CC0-1.0.txt']['hash_key']) - driver.find_element_by_link_text('LICENSES/CC0-1.0.txt').click() - WebDriverWait(driver, 10).until(lambda _: dialog_container.is_displayed()) - assert 'list_disabled' in ul.get_attribute('class') - assert not preview_container.is_displayed() - - msg = execute_in_page('returnval(list_ctx.dialog_ctx.msg.textContent);') - assert msg == "File missing from Haketilo's internal database :(" - - execute_in_page('returnval(list_ctx.dialog_ctx.ok_but.click());') - WebDriverWait(driver, 10).until(lambda _: preview_container.is_displayed()) - # Check that item removal failure causes the right error dialog to appear. execute_in_page('haketilodb.finalize_transaction = () => {throw "sth";};') remove_current_item() diff --git a/test/unit/test_item_preview.py b/test/unit/test_item_preview.py index 6148bc2..8b2b161 100644 --- a/test/unit/test_item_preview.py +++ b/test/unit/test_item_preview.py @@ -19,6 +19,7 @@ Haketilo unit tests - displaying resources and mappings details import pytest from selenium.webdriver.support.ui import WebDriverWait +from selenium.common.exceptions import NoSuchWindowException from ..extension_crafting import ExtraHTML from ..script_loader import load_script @@ -138,7 +139,10 @@ def test_mapping_preview(driver, execute_in_page): @pytest.mark.ext_data({ 'background_script': broker_js, - 'extra_html': ExtraHTML('html/item_preview.html', {}), + 'extra_html': [ + ExtraHTML('html/item_preview.html', {}), + ExtraHTML('html/file_preview.html', {}, wrap_into_htmldoc=False) + ], 'navigate_to': 'html/item_preview.html' }) @pytest.mark.usefixtures('webextension') @@ -148,8 +152,6 @@ def test_file_preview_link(driver, execute_in_page): referenced file to be previewed. """ execute_in_page(load_script('html/item_preview.js')) - # Mock dialog - execute_in_page('dialog.error = (...args) => window.error_args = args;') sample_data = make_complete_sample_data() sample_data['mappings'] = {} @@ -162,22 +164,45 @@ def test_file_preview_link(driver, execute_in_page): execute_in_page( ''' - let resource_preview_object = - resource_preview(arguments[0], undefined, "dummy dialog ctx"); + let resource_preview_object = resource_preview(arguments[0], undefined); document.body.append(resource_preview_object.main_div); ''', sample_resource) window0 = driver.window_handles[0] driver.find_element_by_link_text('hello.js').click() - WebDriverWait(driver, 10).until(lambda d: len(d.window_handles) > 1) - window1 = [wh for wh in driver.window_handles if wh != window0][0] - driver.switch_to.window(window1) - assert sample_files['hello.js']['contents'] in driver.page_source + def blob_url_navigated(driver): + if len(driver.window_handles) < 2: + return + window1 = [wh for wh in driver.window_handles if wh != window0][0] + driver.switch_to.window(window1) + try: + return driver.current_url.startswith('blob') + except NoSuchWindowException: + pass + + WebDriverWait(driver, 10).until(blob_url_navigated) + + assert sample_files['hello.js']['contents'].strip() \ + in driver.find_element_by_tag_name("pre").text + + driver.close() driver.switch_to.window(window0) + driver.find_element_by_link_text('bye.js').click() - assert driver.execute_script('return window.error_args;') == [ - 'dummy dialog ctx', - "File missing from Haketilo's internal database :(" - ] + + def get_error_span(driver): + if len(driver.window_handles) < 2: + return + window1 = [wh for wh in driver.window_handles if wh != window0][0] + driver.switch_to.window(window1) + try: + return driver.find_element_by_id('error_msg') + except NoSuchWindowException: + pass + + error_span = WebDriverWait(driver, 10).until(get_error_span) + assert error_span.is_displayed() + assert "Couldn't find file in Haketilo's internal database :(" \ + in error_span.text diff --git a/test/unit/test_patterns_query_manager.py b/test/unit/test_patterns_query_manager.py index 5daf3a0..4662e8a 100644 --- a/test/unit/test_patterns_query_manager.py +++ b/test/unit/test_patterns_query_manager.py @@ -20,6 +20,7 @@ Haketilo unit tests - building pattern tree and putting it in a content script import pytest import json from selenium.webdriver.support.ui import WebDriverWait +from selenium.common.exceptions import TimeoutException from ..script_loader import load_script @@ -240,25 +241,31 @@ def background_js(): @pytest.mark.usefixtures('webextension') def test_pqm_script_injection(driver, execute_in_page): # Let's open a normal page in a second window. Window 0 will be used to make - # changed to IndexedDB and window 1 to test the working of content scripts. + # changes to IndexedDB and window 1 to test the working of content scripts. driver.execute_script('window.open("about:blank", "_blank");') WebDriverWait(driver, 10).until(lambda d: len(d.window_handles) == 2) windows = [*driver.window_handles] - def run_content_script(): - driver.switch_to.window(windows[1]) - driver.get('https://gotmyowndoma.in/index.html') - windows[1] = driver.window_handles[1] + def get_tree_json(driver): return driver.execute_script( ''' return (document.getElementById("tree-json") || {}).innerText; ''') - for attempt in range(10): + def run_content_script(): + driver.switch_to.window(windows[1]) + driver.get('https://gotmyowndoma.in/index.html') + windows[1] = driver.current_window_handle + try: + return WebDriverWait(driver, 10).until(get_tree_json) + except TimeoutException: + pass + + for attempt in range(2): json_txt = run_content_script() - if json.loads(json_txt) == {}: + if json_txt and json.loads(json_txt) == {}: break; - assert attempt != 9 + assert attempt != 2 driver.switch_to.window(windows[0]) execute_in_page(load_script('common/indexeddb.js')) @@ -271,12 +278,12 @@ def test_pqm_script_injection(driver, execute_in_page): } execute_in_page('returnval(save_items(arguments[0]));', sample_data) - for attempt in range(10): - tree_json = run_content_script() + for attempt in range(2): + tree_json = run_content_script() or '{}' json.loads(tree_json) if all([m['identifier'] in tree_json for m in sample_mappings]): break - assert attempt != 9 + assert attempt != 2 driver.switch_to.window(windows[0]) execute_in_page( diff --git a/test/unit/test_popup.py b/test/unit/test_popup.py index da1812d..da125ec 100644 --- a/test/unit/test_popup.py +++ b/test/unit/test_popup.py @@ -20,6 +20,7 @@ Haketilo unit tests - repository querying import pytest import json from selenium.webdriver.support.ui import WebDriverWait +from selenium.common.exceptions import ElementNotInteractableException from ..extension_crafting import ExtraHTML from ..script_loader import load_script @@ -146,10 +147,16 @@ def test_popup_display(driver, execute_in_page, page_info_key): """ reload_with_target(driver, f'mock_page_info-{page_info_key}') - by_id = driver.execute_script(''' - const nodes = [...document.querySelectorAll("[id]")]; - return nodes.reduce((ob, node) => Object.assign(ob, {[node.id]: node}), {}); - '''); + def get_nodes_by_id(driver): + by_id = driver.execute_script( + ''' + const nodes = [...document.querySelectorAll("[id]")]; + const reductor = (ob, node) => Object.assign(ob, {[node.id]: node}); + return nodes.reduce(reductor, {}); + '''); + return by_id if by_id and 'repo_query_container' in by_id else None + + by_id = WebDriverWait(driver, 10).until(get_nodes_by_id) if page_info_key == '': error_msg = 'Page info not avaialable. Try reloading the page.' @@ -213,9 +220,22 @@ def test_popup_repo_query(driver, execute_in_page): """ reload_with_target(driver, f'mock_page_info-blocked_rule') + driver.implicitly_wait(10) search_but = driver.find_element_by_id("search_resources_but") - WebDriverWait(driver, 10).until(lambda d: search_but.is_displayed()) - search_but.click() + driver.implicitly_wait(0) + + # For unknown reasons waiting for search_but.is_displayed() to return True + # does not guarantee the button will be interactable afterwards under newer + # browsers. Hence, this workaround. + def click_search_but(driver): + try: + search_but.click() + return True + except ElementNotInteractableException: + pass + + WebDriverWait(driver, 10).until(click_search_but) + containers = dict([(name, driver.find_element_by_id(f'{name}_container')) for name in ('page_info', 'repo_query')]) assert not containers['page_info'].is_displayed() |