From 2b5e8daba059946fd05050d20364cd00803826eb Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Tue, 7 Nov 2017 11:52:40 -0500 Subject: [PATCH 01/16] Implement qtwebengine version of JSTester --- tests/unit/javascript/conftest.py | 97 +++++++++++++++++++ tests/unit/javascript/stylesheet/simple.html | 4 + .../javascript/stylesheet/test_stylesheet.py | 65 +++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 tests/unit/javascript/stylesheet/simple.html create mode 100644 tests/unit/javascript/stylesheet/test_stylesheet.py diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py index b9f013ecf..788daf568 100644 --- a/tests/unit/javascript/conftest.py +++ b/tests/unit/javascript/conftest.py @@ -25,10 +25,12 @@ import logging import pytest import jinja2 +from tests.helpers.fixtures import CallbackChecker try: from PyQt5.QtWebKit import QWebSettings from PyQt5.QtWebKitWidgets import QWebPage + from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineSettings except ImportError: # FIXME:qtwebengine Make these tests use the tab API QWebSettings = None @@ -68,6 +70,34 @@ else: """Fail tests on js console messages as they're used for errors.""" pytest.fail("js console ({}:{}): {}".format(source, line, msg)) + class TestWebEnginePage(QWebEnginePage): + + """QWebPage subclass which overrides some test methods. + + Attributes: + _logger: The logger used for alerts. + """ + + def __init__(self, parent=None): + super().__init__(parent) + self._logger = logging.getLogger('js-tests') + + def javaScriptAlert(self, _frame, msg): + """Log javascript alerts.""" + self._logger.info("js alert: {}".format(msg)) + + def javaScriptConfirm(self, _frame, msg): + """Fail tests on js confirm() as that should never happen.""" + pytest.fail("js confirm: {}".format(msg)) + + def javaScriptPrompt(self, _frame, msg, _default): + """Fail tests on js prompt() as that should never happen.""" + pytest.fail("js prompt: {}".format(msg)) + + def javaScriptConsoleMessage(self, level, msg, line, source): + """Fail tests on js console messages as they're used for errors.""" + pytest.fail("[{}] js console ({}:{}): {}".format(level, source, line, msg)) + class JSTester: @@ -133,8 +163,75 @@ class JSTester: QWebSettings.JavascriptEnabled) return self.webview.page().mainFrame().evaluateJavaScript(source) +class JSWebEngineTester: + + """Object returned by js_tester_webengine which provides test data and a webview. + + Attributes: + webview: The webview which is used. + _qtbot: The QtBot fixture from pytest-qt. + _jinja_env: The jinja2 environment used to get templates. + """ + + def __init__(self, webview, callback_checker, qtbot): + self.webview = webview + self.webview.setPage(TestWebEnginePage(self.webview)) + self.callback_checker = callback_checker + self._qtbot = qtbot + loader = jinja2.FileSystemLoader(os.path.dirname(__file__)) + self._jinja_env = jinja2.Environment(loader=loader, autoescape=True) + + def load(self, path, **kwargs): + """Load and display the given test data. + + Args: + path: The path to the test file, relative to the javascript/ + folder. + **kwargs: Passed to jinja's template.render(). + """ + template = self._jinja_env.get_template(path) + with self._qtbot.waitSignal(self.webview.loadFinished) as blocker: + self.webview.setHtml(template.render(**kwargs)) + assert blocker.args == [True] + + def run_file(self, filename, expected): + """Run a javascript file. + + Args: + filename: The javascript filename, relative to + qutebrowser/javascript. + + Return: + The javascript return value. + """ + source = utils.read_file(os.path.join('javascript', filename)) + self.run(source, expected) + + def run(self, source, expected): + """Run the given javascript source. + + Args: + source: The source to run as a string. + + Return: + The javascript return value. + """ + # TODO how to do this properly + callback_checker = CallbackChecker(self._qtbot) + assert self.webview.settings().testAttribute(QWebEngineSettings.JavascriptEnabled) + self.webview.page().runJavaScript(source, callback_checker.callback) + callback_checker.check(expected) + @pytest.fixture def js_tester(webview, qtbot): """Fixture to test javascript snippets.""" return JSTester(webview, qtbot) + + +@pytest.fixture +def js_tester_webengine(callback_checker, webengineview, qtbot): + """Fixture to test javascript snippets.""" + webengineview.settings().setAttribute( + QWebEngineSettings.JavascriptEnabled, True) + return JSWebEngineTester(webengineview, callback_checker, qtbot) diff --git a/tests/unit/javascript/stylesheet/simple.html b/tests/unit/javascript/stylesheet/simple.html new file mode 100644 index 000000000..4073672a4 --- /dev/null +++ b/tests/unit/javascript/stylesheet/simple.html @@ -0,0 +1,4 @@ +{% extends "base.html" %} +{% block content %} +

Hello World!

+{% endblock %} diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py new file mode 100644 index 000000000..38c1fd116 --- /dev/null +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -0,0 +1,65 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2015-2017 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser 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. +# +# qutebrowser 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 qutebrowser. If not, see . + +"""Tests for position_caret.js.""" + +import os +import pytest +from qutebrowser.utils import javascript +from qutebrowser.browser import shared +from qutebrowser.config import config +from PyQt5.QtWebEngineWidgets import QWebEngineSettings + +class StylesheetTester: + + """Helper class (for the caret_tester fixture) for asserts. + + Attributes: + js: The js_tester fixture. + """ + + def __init__(self, js_tester): + self.js = js_tester + + def check(self): + """Check whether the caret is before the MARKER text.""" + self.js.run('window._qutebrowser = window._qutebrowser || {};', {}) + self.js.run_file('stylesheet.js', {}) + code = javascript.assemble('stylesheet', 'set_css', + "body {background-color: lightblue;}") + self.js.run(code, None) + self.js.run("window.getComputedStyle(document.body, null).getPropertyValue('background-color')", "rgb(173, 216, 230)") + + +@pytest.fixture +@pytest.mark.usefixtures('redirect_webengine_data') +def stylesheet_tester(js_tester_webengine): + """Helper fixture to test caret browsing positions.""" + ss_tester = StylesheetTester(js_tester_webengine) + # Showing webview here is necessary for test_scrolled_down_img to + # succeed in some cases, see #1988 + ss_tester.js.webview.show() + return ss_tester + + +@pytest.mark.integration +def test_simple(stylesheet_tester): + """Test with a simple (one-line) HTML text.""" + stylesheet_tester.js.load('stylesheet/simple.html') + stylesheet_tester.check() From e7fdff56329c0f3de859cf3255b58202d4c0a227 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Wed, 8 Nov 2017 12:27:54 -0500 Subject: [PATCH 02/16] Implement basic stylesheet tests --- tests/helpers/fixtures.py | 5 +- .../javascript/stylesheet/test_stylesheet.py | 46 ++++++++++++++----- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index a01c72788..6fe64a2d2 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -100,7 +100,10 @@ class CallbackChecker(QObject): if self._result is self.UNSET: with self._qtbot.waitSignal(self.got_result, timeout=2000): pass - assert self._result == expected + self._assert_result(self._result, expected) + + def _assert_result(self, result, expected): + assert result == expected @pytest.fixture diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index 38c1fd116..843929eb1 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -26,6 +26,8 @@ from qutebrowser.browser import shared from qutebrowser.config import config from PyQt5.QtWebEngineWidgets import QWebEngineSettings +DEFAULT_BODY_BG = "rgba(0, 0, 0, 0)" + class StylesheetTester: """Helper class (for the caret_tester fixture) for asserts. @@ -37,18 +39,24 @@ class StylesheetTester: def __init__(self, js_tester): self.js = js_tester - def check(self): - """Check whether the caret is before the MARKER text.""" + def init_stylesheet(self): + """Initializes stylesheet. + Run after document is loaded.""" self.js.run('window._qutebrowser = window._qutebrowser || {};', {}) self.js.run_file('stylesheet.js', {}) - code = javascript.assemble('stylesheet', 'set_css', - "body {background-color: lightblue;}") + + def set_css(self, css): + """Set css to CSS via stylesheet.js.""" + code = javascript.assemble('stylesheet', 'set_css', css) self.js.run(code, None) - self.js.run("window.getComputedStyle(document.body, null).getPropertyValue('background-color')", "rgb(173, 216, 230)") + + def check_set(self, element, value): + """Check whether the css in ELEMENT is set to VALUE.""" + self.js.run("window.getComputedStyle(document.body, null)" + ".getPropertyValue('{}');".format(element), value) @pytest.fixture -@pytest.mark.usefixtures('redirect_webengine_data') def stylesheet_tester(js_tester_webengine): """Helper fixture to test caret browsing positions.""" ss_tester = StylesheetTester(js_tester_webengine) @@ -57,9 +65,25 @@ def stylesheet_tester(js_tester_webengine): ss_tester.js.webview.show() return ss_tester - -@pytest.mark.integration -def test_simple(stylesheet_tester): - """Test with a simple (one-line) HTML text.""" +def test_no_set_stylesheet(stylesheet_tester): stylesheet_tester.js.load('stylesheet/simple.html') - stylesheet_tester.check() + stylesheet_tester.init_stylesheet() + stylesheet_tester.check_set("background-color", DEFAULT_BODY_BG) + +def test_no_set_stylesheet_no_load(stylesheet_tester): + stylesheet_tester.js.load('stylesheet/simple.html') + stylesheet_tester.check_set("background-color", DEFAULT_BODY_BG) + +def test_simple_set_bg(stylesheet_tester): + stylesheet_tester.js.load('stylesheet/simple.html') + stylesheet_tester.init_stylesheet() + stylesheet_tester.set_css("body {background-color: rgb(10, 10, 10);}") + stylesheet_tester.check_set("background-color", "rgb(10, 10, 10)") + +def test_simple_set_clear_bg(stylesheet_tester): + stylesheet_tester.js.load('stylesheet/simple.html') + stylesheet_tester.init_stylesheet() + stylesheet_tester.set_css("body {background-color: rgb(10, 10, 10);}") + stylesheet_tester.check_set("background-color", "rgb(10, 10, 10)") + stylesheet_tester.set_css("") + stylesheet_tester.check_set("background-color", DEFAULT_BODY_BG) From 9a1d10ca11070598c7f8c3a72496880f28ee6d0c Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Wed, 8 Nov 2017 12:59:59 -0500 Subject: [PATCH 03/16] Add tests which override existing css --- .../stylesheet/simple_bg_set_red.html | 9 +++++++ .../javascript/stylesheet/test_stylesheet.py | 26 ++++++++++++------- 2 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 tests/unit/javascript/stylesheet/simple_bg_set_red.html diff --git a/tests/unit/javascript/stylesheet/simple_bg_set_red.html b/tests/unit/javascript/stylesheet/simple_bg_set_red.html new file mode 100644 index 000000000..b40352340 --- /dev/null +++ b/tests/unit/javascript/stylesheet/simple_bg_set_red.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} +{% block style %} +body { + background-color: rgb(255, 0, 0); +} +{% endblock %} +{% block content %} +

Hello World!

+{% endblock %} diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index 843929eb1..9057f249b 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -65,17 +65,23 @@ def stylesheet_tester(js_tester_webengine): ss_tester.js.webview.show() return ss_tester -def test_no_set_stylesheet(stylesheet_tester): - stylesheet_tester.js.load('stylesheet/simple.html') - stylesheet_tester.init_stylesheet() - stylesheet_tester.check_set("background-color", DEFAULT_BODY_BG) +@pytest.mark.parametrize('init', [False, True]) +@pytest.mark.parametrize('page,expected', [('stylesheet/simple.html', DEFAULT_BODY_BG), + ('stylesheet/simple_bg_set_red.html', "rgb(255, 0, 0)")]) +def test_no_set_stylesheet(stylesheet_tester, init, page, expected): + stylesheet_tester.js.load(page) + if init: + stylesheet_tester.init_stylesheet() + stylesheet_tester.check_set("background-color", expected) -def test_no_set_stylesheet_no_load(stylesheet_tester): - stylesheet_tester.js.load('stylesheet/simple.html') - stylesheet_tester.check_set("background-color", DEFAULT_BODY_BG) - -def test_simple_set_bg(stylesheet_tester): - stylesheet_tester.js.load('stylesheet/simple.html') +@pytest.mark.parametrize('page', ['stylesheet/simple.html', + 'stylesheet/simple_bg_set_red.html']) +@pytest.mark.parametrize('set_js', [True, False]) +def test_simple_set_bg(stylesheet_tester, page, set_js): + stylesheet_tester.js.load(page) + if set_js: + stylesheet_tester.js.run('document.body.style.backgroundColor = "red";', 'red') + pytest.xfail("overring values set with js does not work.") stylesheet_tester.init_stylesheet() stylesheet_tester.set_css("body {background-color: rgb(10, 10, 10);}") stylesheet_tester.check_set("background-color", "rgb(10, 10, 10)") From 03eae9140e4cfa94a884145d26fafd26dd71d60c Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Wed, 8 Nov 2017 22:58:35 -0500 Subject: [PATCH 04/16] Implement proper loading of stylesheet.js --- tests/unit/javascript/conftest.py | 24 +++++-- tests/unit/javascript/stylesheet/green.css | 1 + tests/unit/javascript/stylesheet/none.css | 0 tests/unit/javascript/stylesheet/simple.xml | 43 ++++++++++++ .../javascript/stylesheet/test_stylesheet.py | 65 ++++++++++--------- 5 files changed, 99 insertions(+), 34 deletions(-) create mode 100644 tests/unit/javascript/stylesheet/green.css create mode 100644 tests/unit/javascript/stylesheet/none.css create mode 100644 tests/unit/javascript/stylesheet/simple.xml diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py index 788daf568..25fc9ea9d 100644 --- a/tests/unit/javascript/conftest.py +++ b/tests/unit/javascript/conftest.py @@ -28,9 +28,10 @@ import jinja2 from tests.helpers.fixtures import CallbackChecker try: + from PyQt5.QtCore import QUrl, QFile, QFileInfo from PyQt5.QtWebKit import QWebSettings from PyQt5.QtWebKitWidgets import QWebPage - from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineSettings + from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineSettings, QWebEngineScript except ImportError: # FIXME:qtwebengine Make these tests use the tab API QWebSettings = None @@ -194,6 +195,19 @@ class JSWebEngineTester: self.webview.setHtml(template.render(**kwargs)) assert blocker.args == [True] + def load_file(self, path: str): + """Loads a file from disk""" + self.load_url(QUrl.fromLocalFile( + os.path.join(os.path.dirname(__file__), path))) + + def load_url(self, url: QUrl): + """Load a given QUrl.""" + with self._qtbot.waitSignal(self.webview.loadFinished) as blocker: + self.webview.load(url) + assert blocker.args == [True] + import time + time.sleep(1) + def run_file(self, filename, expected): """Run a javascript file. @@ -207,7 +221,7 @@ class JSWebEngineTester: source = utils.read_file(os.path.join('javascript', filename)) self.run(source, expected) - def run(self, source, expected): + def run(self, source, expected, world=QWebEngineScript.ApplicationWorld): """Run the given javascript source. Args: @@ -218,8 +232,10 @@ class JSWebEngineTester: """ # TODO how to do this properly callback_checker = CallbackChecker(self._qtbot) - assert self.webview.settings().testAttribute(QWebEngineSettings.JavascriptEnabled) - self.webview.page().runJavaScript(source, callback_checker.callback) + assert self.webview.settings().testAttribute( + QWebEngineSettings.JavascriptEnabled) + self.webview.page().runJavaScript(source, world, + callback_checker.callback) callback_checker.check(expected) diff --git a/tests/unit/javascript/stylesheet/green.css b/tests/unit/javascript/stylesheet/green.css new file mode 100644 index 000000000..35832971a --- /dev/null +++ b/tests/unit/javascript/stylesheet/green.css @@ -0,0 +1 @@ +body {background-color: rgb(0, 255, 0);} diff --git a/tests/unit/javascript/stylesheet/none.css b/tests/unit/javascript/stylesheet/none.css new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/javascript/stylesheet/simple.xml b/tests/unit/javascript/stylesheet/simple.xml new file mode 100644 index 000000000..bac4539ab --- /dev/null +++ b/tests/unit/javascript/stylesheet/simple.xml @@ -0,0 +1,43 @@ + + + + org.qutebrowser.qutebrowser + CC-BY-SA-3.0 + GPL-3.0 + qutebrowser + A keyboard-driven web browser + +

+ qutebrowser is a keyboard-focused browser with a minimal GUI. + It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl, + and is based on Python and PyQt5. +

+
+ + Network + WebBrowser + + + qutebrowser + + qutebrowser.desktop + + + https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/main.png + + + https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/downloads.png + + + https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/completion.png + + + https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/hints.png + + + https://www.qutebrowser.org + https://qutebrowser.org/doc/faq.html + https://qutebrowser.org/doc/help/ + https://github.com/qutebrowser/qutebrowser/issues/ + https://github.com/qutebrowser/qutebrowser#donating +
diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index 9057f249b..ee557b2a1 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -21,12 +21,16 @@ import os import pytest -from qutebrowser.utils import javascript +from qutebrowser.utils import javascript, utils from qutebrowser.browser import shared from qutebrowser.config import config -from PyQt5.QtWebEngineWidgets import QWebEngineSettings +from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineProfile, QWebEngineScript +import qutebrowser.browser.webengine.webenginesettings as webenginesettings DEFAULT_BODY_BG = "rgba(0, 0, 0, 0)" +GREEN_BODY_BG = "rgb(0, 255, 0)" +CSS_BODY_GREEN = "body {background-color: rgb(0, 255, 0);}" +CSS_BODY_RED = "body {background-color: rgb(255, 0, 0);}" class StylesheetTester: @@ -36,60 +40,61 @@ class StylesheetTester: js: The js_tester fixture. """ - def __init__(self, js_tester): + def __init__(self, js_tester, config_stub): self.js = js_tester + self.config_stub = config_stub - def init_stylesheet(self): - """Initializes stylesheet. - Run after document is loaded.""" - self.js.run('window._qutebrowser = window._qutebrowser || {};', {}) - self.js.run_file('stylesheet.js', {}) + def init_stylesheet(self, css_file="green.css"): + + self.config_stub.val.content.user_stylesheets = \ + os.path.join(os.path.dirname(__file__), css_file) + p = QWebEngineProfile.defaultProfile() + webenginesettings._init_stylesheet(p) def set_css(self, css): """Set css to CSS via stylesheet.js.""" code = javascript.assemble('stylesheet', 'set_css', css) self.js.run(code, None) - def check_set(self, element, value): + def check_set(self, value, element="background-color"): """Check whether the css in ELEMENT is set to VALUE.""" self.js.run("window.getComputedStyle(document.body, null)" ".getPropertyValue('{}');".format(element), value) @pytest.fixture -def stylesheet_tester(js_tester_webengine): +def stylesheet_tester(js_tester_webengine, config_stub): """Helper fixture to test caret browsing positions.""" - ss_tester = StylesheetTester(js_tester_webengine) + ss_tester = StylesheetTester(js_tester_webengine, config_stub) # Showing webview here is necessary for test_scrolled_down_img to # succeed in some cases, see #1988 ss_tester.js.webview.show() return ss_tester -@pytest.mark.parametrize('init', [False, True]) -@pytest.mark.parametrize('page,expected', [('stylesheet/simple.html', DEFAULT_BODY_BG), - ('stylesheet/simple_bg_set_red.html', "rgb(255, 0, 0)")]) -def test_no_set_stylesheet(stylesheet_tester, init, page, expected): - stylesheet_tester.js.load(page) - if init: - stylesheet_tester.init_stylesheet() - stylesheet_tester.check_set("background-color", expected) - @pytest.mark.parametrize('page', ['stylesheet/simple.html', 'stylesheet/simple_bg_set_red.html']) @pytest.mark.parametrize('set_js', [True, False]) -def test_simple_set_bg(stylesheet_tester, page, set_js): +def test_set_delayed(stylesheet_tester, page, set_js): + stylesheet_tester.init_stylesheet("none.css") stylesheet_tester.js.load(page) if set_js: - stylesheet_tester.js.run('document.body.style.backgroundColor = "red";', 'red') + stylesheet_tester.js.run( + 'document.body.style.backgroundColor = "red";', 'red') pytest.xfail("overring values set with js does not work.") - stylesheet_tester.init_stylesheet() - stylesheet_tester.set_css("body {background-color: rgb(10, 10, 10);}") - stylesheet_tester.check_set("background-color", "rgb(10, 10, 10)") + stylesheet_tester.set_css("body {background-color: rgb(0, 255, 0);}") + stylesheet_tester.check_set("rgb(0, 255, 0)") -def test_simple_set_clear_bg(stylesheet_tester): - stylesheet_tester.js.load('stylesheet/simple.html') +@pytest.mark.parametrize('page', ['stylesheet/simple.html', + 'stylesheet/simple_bg_set_red.html']) +def test_set_clear_bg(stylesheet_tester, page): stylesheet_tester.init_stylesheet() - stylesheet_tester.set_css("body {background-color: rgb(10, 10, 10);}") - stylesheet_tester.check_set("background-color", "rgb(10, 10, 10)") + stylesheet_tester.js.load('stylesheet/simple.html') + stylesheet_tester.check_set(GREEN_BODY_BG) stylesheet_tester.set_css("") - stylesheet_tester.check_set("background-color", DEFAULT_BODY_BG) + stylesheet_tester.check_set(DEFAULT_BODY_BG) + +def test_no_set_xml(stylesheet_tester): + stylesheet_tester.init_stylesheet() + pytest.xfail("loading xml files throws exceptions") + stylesheet_tester.js.load_file('stylesheet/simple.xml') + stylesheet_tester.check_set(DEFAULT_BODY_BG) From 04b66e1a0ab7592bee41909d0bfa1a4aa15a34c8 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Wed, 8 Nov 2017 23:14:26 -0500 Subject: [PATCH 05/16] Add a test for svg files --- .../javascript/stylesheet/test_stylesheet.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index ee557b2a1..bae5f3e4d 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""Tests for position_caret.js.""" +"""Tests for stylesheet.js.""" import os import pytest @@ -34,10 +34,11 @@ CSS_BODY_RED = "body {background-color: rgb(255, 0, 0);}" class StylesheetTester: - """Helper class (for the caret_tester fixture) for asserts. + """Helper class (for the stylesheet_tester fixture) for asserts. Attributes: js: The js_tester fixture. + config_stub: The config stub object. """ def __init__(self, js_tester, config_stub): @@ -64,10 +65,8 @@ class StylesheetTester: @pytest.fixture def stylesheet_tester(js_tester_webengine, config_stub): - """Helper fixture to test caret browsing positions.""" + """Helper fixture to test stylesheets""" ss_tester = StylesheetTester(js_tester_webengine, config_stub) - # Showing webview here is necessary for test_scrolled_down_img to - # succeed in some cases, see #1988 ss_tester.js.webview.show() return ss_tester @@ -75,6 +74,7 @@ def stylesheet_tester(js_tester_webengine, config_stub): 'stylesheet/simple_bg_set_red.html']) @pytest.mark.parametrize('set_js', [True, False]) def test_set_delayed(stylesheet_tester, page, set_js): + """Test a delayed invocation of set_css.""" stylesheet_tester.init_stylesheet("none.css") stylesheet_tester.js.load(page) if set_js: @@ -87,6 +87,7 @@ def test_set_delayed(stylesheet_tester, page, set_js): @pytest.mark.parametrize('page', ['stylesheet/simple.html', 'stylesheet/simple_bg_set_red.html']) def test_set_clear_bg(stylesheet_tester, page): + """Test setting and clearing the stylesheet""" stylesheet_tester.init_stylesheet() stylesheet_tester.js.load('stylesheet/simple.html') stylesheet_tester.check_set(GREEN_BODY_BG) @@ -94,7 +95,19 @@ def test_set_clear_bg(stylesheet_tester, page): stylesheet_tester.check_set(DEFAULT_BODY_BG) def test_no_set_xml(stylesheet_tester): + """Test stylesheet never modifies xml files.""" stylesheet_tester.init_stylesheet() - pytest.xfail("loading xml files throws exceptions") + pytest.xfail("loading xml/svg files throws exceptions") stylesheet_tester.js.load_file('stylesheet/simple.xml') stylesheet_tester.check_set(DEFAULT_BODY_BG) + stylesheet_tester.set_css("body {background-color: rgb(0, 255, 0);}") + stylesheet_tester.check_set(DEFAULT_BODY_BG) + +def test_no_set_svg(stylesheet_tester): + """Test stylesheet never modifies svg files.""" + stylesheet_tester.init_stylesheet() + pytest.xfail("loading xml/svg files throws exceptions") + stylesheet_tester.js.load_file('../../../misc/cheatsheet.svg') + stylesheet_tester.check_set(None) + stylesheet_tester.set_css("body {background-color: rgb(0, 255, 0);}") + stylesheet_tester.check_set(None) From 2f9a857a27130f7e12ff17f9695e004c42c8ca4e Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Wed, 8 Nov 2017 23:46:12 -0500 Subject: [PATCH 06/16] Add test for styling error pages --- tests/unit/javascript/conftest.py | 9 +++++---- tests/unit/javascript/stylesheet/test_stylesheet.py | 6 ++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py index 25fc9ea9d..fa8c48f27 100644 --- a/tests/unit/javascript/conftest.py +++ b/tests/unit/javascript/conftest.py @@ -195,16 +195,17 @@ class JSWebEngineTester: self.webview.setHtml(template.render(**kwargs)) assert blocker.args == [True] - def load_file(self, path: str): + def load_file(self, path: str, force=False): """Loads a file from disk""" self.load_url(QUrl.fromLocalFile( - os.path.join(os.path.dirname(__file__), path))) + os.path.join(os.path.dirname(__file__), path)), force) - def load_url(self, url: QUrl): + def load_url(self, url: QUrl, force=False): """Load a given QUrl.""" with self._qtbot.waitSignal(self.webview.loadFinished) as blocker: self.webview.load(url) - assert blocker.args == [True] + if not force: + assert blocker.args == [True] import time time.sleep(1) diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index bae5f3e4d..736111619 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -111,3 +111,9 @@ def test_no_set_svg(stylesheet_tester): stylesheet_tester.check_set(None) stylesheet_tester.set_css("body {background-color: rgb(0, 255, 0);}") stylesheet_tester.check_set(None) + +def test_set_error(stylesheet_tester): + """Test stylesheet modifies file not found error pages.""" + stylesheet_tester.init_stylesheet() + stylesheet_tester.js.load_file('non-existent.html', force=True) + stylesheet_tester.check_set(GREEN_BODY_BG) From 5ac8e5ad3e15974a848ccd05e8cde9c9dd18fa7a Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Thu, 9 Nov 2017 00:07:54 -0500 Subject: [PATCH 07/16] Clean up stylesheet tests --- tests/unit/javascript/conftest.py | 64 ++++++++++++------- tests/unit/javascript/stylesheet/green.css | 2 +- tests/unit/javascript/stylesheet/simple.xml | 38 ----------- .../javascript/stylesheet/test_stylesheet.py | 55 ++++++++-------- 4 files changed, 71 insertions(+), 88 deletions(-) diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py index fa8c48f27..b6b0980d5 100644 --- a/tests/unit/javascript/conftest.py +++ b/tests/unit/javascript/conftest.py @@ -26,17 +26,25 @@ import logging import pytest import jinja2 from tests.helpers.fixtures import CallbackChecker +from PyQt5.QtCore import QUrl try: - from PyQt5.QtCore import QUrl, QFile, QFileInfo from PyQt5.QtWebKit import QWebSettings from PyQt5.QtWebKitWidgets import QWebPage - from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineSettings, QWebEngineScript except ImportError: # FIXME:qtwebengine Make these tests use the tab API QWebSettings = None QWebPage = None +try: + from PyQt5.QtWebEngineWidgets import (QWebEnginePage, + QWebEngineSettings, + QWebEngineScript) +except ImportError: + QWebEnginePage = None + QWebEngineSettings = None + QWebEngineScript = None + from qutebrowser.utils import utils @@ -71,9 +79,12 @@ else: """Fail tests on js console messages as they're used for errors.""" pytest.fail("js console ({}:{}): {}".format(source, line, msg)) +if QWebEnginePage is None: + TestWebEnginePage = None +else: class TestWebEnginePage(QWebEnginePage): - """QWebPage subclass which overrides some test methods. + """QWebEnginePage which overrides javascript logging methods. Attributes: _logger: The logger used for alerts. @@ -97,7 +108,8 @@ else: def javaScriptConsoleMessage(self, level, msg, line, source): """Fail tests on js console messages as they're used for errors.""" - pytest.fail("[{}] js console ({}:{}): {}".format(level, source, line, msg)) + pytest.fail("[{}] js console ({}:{}): {}".format(level, source, + line, msg)) class JSTester: @@ -164,9 +176,10 @@ class JSTester: QWebSettings.JavascriptEnabled) return self.webview.page().mainFrame().evaluateJavaScript(source) + class JSWebEngineTester: - """Object returned by js_tester_webengine which provides test data and a webview. + """Object returned by js_tester_webengine which provides a webview. Attributes: webview: The webview which is used. @@ -183,7 +196,7 @@ class JSWebEngineTester: self._jinja_env = jinja2.Environment(loader=loader, autoescape=True) def load(self, path, **kwargs): - """Load and display the given test data. + """Load and display the given jinja test data. Args: path: The path to the test file, relative to the javascript/ @@ -196,42 +209,47 @@ class JSWebEngineTester: assert blocker.args == [True] def load_file(self, path: str, force=False): - """Loads a file from disk""" + """Load a file from disk. + + Args: + path: The string path from disk to load (relative to this file) + force: Whether to force loading even if the file is invalid. + """ self.load_url(QUrl.fromLocalFile( os.path.join(os.path.dirname(__file__), path)), force) - def load_url(self, url: QUrl, force=False): - """Load a given QUrl.""" + def load_url(self, url: QUrl, force: bool = False): + """Load a given QUrl. + + Args: + url: The QUrl to load. + force: Whether to force loading even if the file is invalid. + """ with self._qtbot.waitSignal(self.webview.loadFinished) as blocker: self.webview.load(url) if not force: assert blocker.args == [True] - import time - time.sleep(1) - def run_file(self, filename, expected): + def run_file(self, filename: str, expected) -> None: """Run a javascript file. Args: filename: The javascript filename, relative to qutebrowser/javascript. - - Return: - The javascript return value. + expected: The value expected return from the javascript execution """ source = utils.read_file(os.path.join('javascript', filename)) self.run(source, expected) - def run(self, source, expected, world=QWebEngineScript.ApplicationWorld): + def run(self, source: str, expected, + world=QWebEngineScript.ApplicationWorld) -> None: """Run the given javascript source. Args: source: The source to run as a string. - - Return: - The javascript return value. + expected: The value expected return from the javascript execution + world: The scope the javascript will run in """ - # TODO how to do this properly callback_checker = CallbackChecker(self._qtbot) assert self.webview.settings().testAttribute( QWebEngineSettings.JavascriptEnabled) @@ -242,13 +260,11 @@ class JSWebEngineTester: @pytest.fixture def js_tester(webview, qtbot): - """Fixture to test javascript snippets.""" + """Fixture to test javascript snippets in webkit.""" return JSTester(webview, qtbot) @pytest.fixture def js_tester_webengine(callback_checker, webengineview, qtbot): - """Fixture to test javascript snippets.""" - webengineview.settings().setAttribute( - QWebEngineSettings.JavascriptEnabled, True) + """Fixture to test javascript snippets in webengine.""" return JSWebEngineTester(webengineview, callback_checker, qtbot) diff --git a/tests/unit/javascript/stylesheet/green.css b/tests/unit/javascript/stylesheet/green.css index 35832971a..b2d035810 100644 --- a/tests/unit/javascript/stylesheet/green.css +++ b/tests/unit/javascript/stylesheet/green.css @@ -1 +1 @@ -body {background-color: rgb(0, 255, 0);} +body, :root {background-color: rgb(0, 255, 0);} diff --git a/tests/unit/javascript/stylesheet/simple.xml b/tests/unit/javascript/stylesheet/simple.xml index bac4539ab..f9073de69 100644 --- a/tests/unit/javascript/stylesheet/simple.xml +++ b/tests/unit/javascript/stylesheet/simple.xml @@ -2,42 +2,4 @@ org.qutebrowser.qutebrowser - CC-BY-SA-3.0 - GPL-3.0 - qutebrowser - A keyboard-driven web browser - -

- qutebrowser is a keyboard-focused browser with a minimal GUI. - It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl, - and is based on Python and PyQt5. -

-
- - Network - WebBrowser - - - qutebrowser - - qutebrowser.desktop - - - https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/main.png - - - https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/downloads.png - - - https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/completion.png - - - https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/hints.png - - - https://www.qutebrowser.org - https://qutebrowser.org/doc/faq.html - https://qutebrowser.org/doc/help/ - https://github.com/qutebrowser/qutebrowser/issues/ - https://github.com/qutebrowser/qutebrowser#donating
diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index 736111619..9be7192e9 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2017 Florian Bruhin (The Compiler) +# Copyright 2017 Jay Kamat # # This file is part of qutebrowser. # @@ -21,17 +21,17 @@ import os import pytest -from qutebrowser.utils import javascript, utils -from qutebrowser.browser import shared -from qutebrowser.config import config -from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineProfile, QWebEngineScript -import qutebrowser.browser.webengine.webenginesettings as webenginesettings +from qutebrowser.utils import javascript +from PyQt5.QtWebEngineWidgets import QWebEngineProfile +from qutebrowser.browser.webengine import webenginesettings + DEFAULT_BODY_BG = "rgba(0, 0, 0, 0)" GREEN_BODY_BG = "rgb(0, 255, 0)" CSS_BODY_GREEN = "body {background-color: rgb(0, 255, 0);}" CSS_BODY_RED = "body {background-color: rgb(255, 0, 0);}" + class StylesheetTester: """Helper class (for the stylesheet_tester fixture) for asserts. @@ -46,71 +46,76 @@ class StylesheetTester: self.config_stub = config_stub def init_stylesheet(self, css_file="green.css"): - - self.config_stub.val.content.user_stylesheets = \ - os.path.join(os.path.dirname(__file__), css_file) + """Initialize the stylesheet with a provided css file.""" + css_path = os.path.join(os.path.dirname(__file__), css_file) + self.config_stub.val.content.user_stylesheets = css_path p = QWebEngineProfile.defaultProfile() webenginesettings._init_stylesheet(p) def set_css(self, css): - """Set css to CSS via stylesheet.js.""" + """Set document style to `css` via stylesheet.js.""" code = javascript.assemble('stylesheet', 'set_css', css) self.js.run(code, None) - def check_set(self, value, element="background-color"): + def check_set(self, value, css_style="background-color", + document_element="document.body"): """Check whether the css in ELEMENT is set to VALUE.""" - self.js.run("window.getComputedStyle(document.body, null)" - ".getPropertyValue('{}');".format(element), value) + self.js.run("window.getComputedStyle({}, null)" + ".getPropertyValue('{}');".format(document_element, + css_style), value) @pytest.fixture def stylesheet_tester(js_tester_webengine, config_stub): - """Helper fixture to test stylesheets""" + """Helper fixture to test stylesheets.""" ss_tester = StylesheetTester(js_tester_webengine, config_stub) ss_tester.js.webview.show() return ss_tester + @pytest.mark.parametrize('page', ['stylesheet/simple.html', 'stylesheet/simple_bg_set_red.html']) -@pytest.mark.parametrize('set_js', [True, False]) -def test_set_delayed(stylesheet_tester, page, set_js): +def test_set_delayed(stylesheet_tester, page): """Test a delayed invocation of set_css.""" stylesheet_tester.init_stylesheet("none.css") stylesheet_tester.js.load(page) - if set_js: - stylesheet_tester.js.run( - 'document.body.style.backgroundColor = "red";', 'red') - pytest.xfail("overring values set with js does not work.") stylesheet_tester.set_css("body {background-color: rgb(0, 255, 0);}") stylesheet_tester.check_set("rgb(0, 255, 0)") + @pytest.mark.parametrize('page', ['stylesheet/simple.html', 'stylesheet/simple_bg_set_red.html']) def test_set_clear_bg(stylesheet_tester, page): - """Test setting and clearing the stylesheet""" + """Test setting and clearing the stylesheet.""" stylesheet_tester.init_stylesheet() stylesheet_tester.js.load('stylesheet/simple.html') stylesheet_tester.check_set(GREEN_BODY_BG) stylesheet_tester.set_css("") stylesheet_tester.check_set(DEFAULT_BODY_BG) + def test_no_set_xml(stylesheet_tester): """Test stylesheet never modifies xml files.""" stylesheet_tester.init_stylesheet() - pytest.xfail("loading xml/svg files throws exceptions") stylesheet_tester.js.load_file('stylesheet/simple.xml') + pytest.xfail("stylesheet is set on xml documents") stylesheet_tester.check_set(DEFAULT_BODY_BG) stylesheet_tester.set_css("body {background-color: rgb(0, 255, 0);}") stylesheet_tester.check_set(DEFAULT_BODY_BG) + def test_no_set_svg(stylesheet_tester): """Test stylesheet never modifies svg files.""" stylesheet_tester.init_stylesheet() - pytest.xfail("loading xml/svg files throws exceptions") + # pytest.xfail("loading xml/svg files throws exceptions") stylesheet_tester.js.load_file('../../../misc/cheatsheet.svg') - stylesheet_tester.check_set(None) + pytest.xfail("stylesheet is set on svg documents??") + stylesheet_tester.check_set(DEFAULT_BODY_BG, + document_element="document.documentElement") stylesheet_tester.set_css("body {background-color: rgb(0, 255, 0);}") - stylesheet_tester.check_set(None) + stylesheet_tester.check_set(DEFAULT_BODY_BG, + document_element="document.documentElement") + def test_set_error(stylesheet_tester): """Test stylesheet modifies file not found error pages.""" From 155ee198cdaa14972bab06656865b9ef7ad2685c Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Thu, 9 Nov 2017 14:44:14 -0500 Subject: [PATCH 08/16] Update stylesheet tests for updates in stylesheet.js --- .../javascript/stylesheet/test_stylesheet.py | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index 9be7192e9..4a6a06c2c 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -64,6 +64,10 @@ class StylesheetTester: ".getPropertyValue('{}');".format(document_element, css_style), value) + def check_eq(self, one, two, true=True): + """Check if one and two are equal.""" + self.js.run("{} === {}".format(one, two), true) + @pytest.fixture def stylesheet_tester(js_tester_webengine, config_stub): @@ -94,27 +98,20 @@ def test_set_clear_bg(stylesheet_tester, page): stylesheet_tester.check_set(DEFAULT_BODY_BG) -def test_no_set_xml(stylesheet_tester): - """Test stylesheet never modifies xml files.""" +def test_set_xml(stylesheet_tester): + """Test stylesheet is applied without altering xml files.""" stylesheet_tester.init_stylesheet() stylesheet_tester.js.load_file('stylesheet/simple.xml') - pytest.xfail("stylesheet is set on xml documents") - stylesheet_tester.check_set(DEFAULT_BODY_BG) - stylesheet_tester.set_css("body {background-color: rgb(0, 255, 0);}") - stylesheet_tester.check_set(DEFAULT_BODY_BG) + stylesheet_tester.check_set(GREEN_BODY_BG) + stylesheet_tester.check_eq("\"html\"", "document.documentElement.nodeName") - -def test_no_set_svg(stylesheet_tester): - """Test stylesheet never modifies svg files.""" +def test_set_svg(stylesheet_tester): + """Test stylesheet is applied for svg files.""" stylesheet_tester.init_stylesheet() - # pytest.xfail("loading xml/svg files throws exceptions") stylesheet_tester.js.load_file('../../../misc/cheatsheet.svg') - pytest.xfail("stylesheet is set on svg documents??") - stylesheet_tester.check_set(DEFAULT_BODY_BG, - document_element="document.documentElement") - stylesheet_tester.set_css("body {background-color: rgb(0, 255, 0);}") - stylesheet_tester.check_set(DEFAULT_BODY_BG, + stylesheet_tester.check_set(GREEN_BODY_BG, document_element="document.documentElement") + stylesheet_tester.check_eq("\"svg\"", "document.documentElement.nodeName") def test_set_error(stylesheet_tester): From d39dda38cef502cbda6a572284c30714bdffa5b0 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Fri, 10 Nov 2017 12:27:00 -0500 Subject: [PATCH 09/16] Refactor CallbackChecker into test utils --- tests/helpers/fixtures.py | 31 ++----------------------------- tests/helpers/utils.py | 26 ++++++++++++++++++++++++++ tests/unit/javascript/conftest.py | 2 +- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index 6fe64a2d2..8caf6fcf5 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -37,13 +37,14 @@ import pytest import py.path # pylint: disable=no-name-in-module import helpers.stubs as stubsmod +from helpers.utils import CallbackChecker from qutebrowser.config import config, configdata, configtypes, configexc from qutebrowser.utils import objreg, standarddir from qutebrowser.browser.webkit import cookies from qutebrowser.misc import savemanager, sql from qutebrowser.keyinput import modeman -from PyQt5.QtCore import pyqtSignal, QEvent, QSize, Qt, QObject +from PyQt5.QtCore import QEvent, QSize, Qt from PyQt5.QtGui import QKeyEvent from PyQt5.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout from PyQt5.QtNetwork import QNetworkCookieJar @@ -78,34 +79,6 @@ class WinRegistryHelper: del objreg.window_registry[win_id] -class CallbackChecker(QObject): - - """Check if a value provided by a callback is the expected one.""" - - got_result = pyqtSignal(object) - UNSET = object() - - def __init__(self, qtbot, parent=None): - super().__init__(parent) - self._qtbot = qtbot - self._result = self.UNSET - - def callback(self, result): - """Callback which can be passed to runJavaScript.""" - self._result = result - self.got_result.emit(result) - - def check(self, expected): - """Wait until the JS result arrived and compare it.""" - if self._result is self.UNSET: - with self._qtbot.waitSignal(self.got_result, timeout=2000): - pass - self._assert_result(self._result, expected) - - def _assert_result(self, result, expected): - assert result == expected - - @pytest.fixture def callback_checker(qtbot): return CallbackChecker(qtbot) diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py index e6e3d37c8..45141f7dc 100644 --- a/tests/helpers/utils.py +++ b/tests/helpers/utils.py @@ -28,6 +28,7 @@ import contextlib import pytest from qutebrowser.utils import qtutils +from PyQt5.QtCore import QObject, pyqtSignal qt58 = pytest.mark.skipif( @@ -176,3 +177,28 @@ def abs_datapath(): @contextlib.contextmanager def nop_contextmanager(): yield + + +class CallbackChecker(QObject): + + """Check if a value provided by a callback is the expected one.""" + + got_result = pyqtSignal(object) + UNSET = object() + + def __init__(self, qtbot, parent=None): + super().__init__(parent) + self._qtbot = qtbot + self._result = self.UNSET + + def callback(self, result): + """Callback which can be passed to runJavaScript.""" + self._result = result + self.got_result.emit(result) + + def check(self, expected): + """Wait until the JS result arrived and compare it.""" + if self._result is self.UNSET: + with self._qtbot.waitSignal(self.got_result, timeout=2000): + pass + assert self._result == expected diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py index b6b0980d5..103c9cf4d 100644 --- a/tests/unit/javascript/conftest.py +++ b/tests/unit/javascript/conftest.py @@ -25,7 +25,7 @@ import logging import pytest import jinja2 -from tests.helpers.fixtures import CallbackChecker +from helpers.utils import CallbackChecker from PyQt5.QtCore import QUrl try: From 83e28a70c5a2cbfd2ed51516e76407ae316cfbab Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Fri, 10 Nov 2017 12:34:23 -0500 Subject: [PATCH 10/16] Fix error message printing for webengine js tests --- tests/unit/javascript/conftest.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py index 103c9cf4d..413609b32 100644 --- a/tests/unit/javascript/conftest.py +++ b/tests/unit/javascript/conftest.py @@ -27,6 +27,7 @@ import pytest import jinja2 from helpers.utils import CallbackChecker from PyQt5.QtCore import QUrl +from qutebrowser.utils.debug import qenum_key try: from PyQt5.QtWebKit import QWebSettings @@ -108,8 +109,8 @@ else: def javaScriptConsoleMessage(self, level, msg, line, source): """Fail tests on js console messages as they're used for errors.""" - pytest.fail("[{}] js console ({}:{}): {}".format(level, source, - line, msg)) + pytest.fail("[{}] js console ({}:{}): {}".format( + qenum_key(QWebEnginePage, level), source, line, msg)) class JSTester: From 324c537a3df65396cf7e1b217bcaa14874620d42 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Fri, 10 Nov 2017 12:55:27 -0500 Subject: [PATCH 11/16] Refactor webkit and webengine js testers to have a common subclass --- tests/unit/javascript/conftest.py | 59 +++++++++---------- .../javascript/stylesheet/test_stylesheet.py | 1 + 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py index 413609b32..bcfc6b47a 100644 --- a/tests/unit/javascript/conftest.py +++ b/tests/unit/javascript/conftest.py @@ -114,8 +114,7 @@ else: class JSTester: - - """Object returned by js_tester which provides test data and a webview. + """Common subclass providing basic functionality for all JS testers. Attributes: webview: The webview which is used. @@ -125,21 +124,12 @@ class JSTester: def __init__(self, webview, qtbot): self.webview = webview - self.webview.setPage(TestWebPage(self.webview)) self._qtbot = qtbot loader = jinja2.FileSystemLoader(os.path.dirname(__file__)) self._jinja_env = jinja2.Environment(loader=loader, autoescape=True) - def scroll_anchor(self, name): - """Scroll the main frame to the given anchor.""" - page = self.webview.page() - old_pos = page.mainFrame().scrollPosition() - page.mainFrame().scrollToAnchor(name) - new_pos = page.mainFrame().scrollPosition() - assert old_pos != new_pos - def load(self, path, **kwargs): - """Load and display the given test data. + """Load and display the given jinja test data. Args: path: The path to the test file, relative to the javascript/ @@ -151,6 +141,29 @@ class JSTester: self.webview.setHtml(template.render(**kwargs)) assert blocker.args == [True] + +class JSWebKitTester(JSTester): + + """Object returned by js_tester which provides test data and a webview. + + Attributes: + webview: The webview which is used. + _qtbot: The QtBot fixture from pytest-qt. + _jinja_env: The jinja2 environment used to get templates. + """ + + def __init__(self, webview, qtbot): + JSTester.__init__(self, webview, qtbot) + self.webview.setPage(TestWebPage(self.webview)) + + def scroll_anchor(self, name): + """Scroll the main frame to the given anchor.""" + page = self.webview.page() + old_pos = page.mainFrame().scrollPosition() + page.mainFrame().scrollToAnchor(name) + new_pos = page.mainFrame().scrollPosition() + assert old_pos != new_pos + def run_file(self, filename): """Run a javascript file. @@ -178,7 +191,7 @@ class JSTester: return self.webview.page().mainFrame().evaluateJavaScript(source) -class JSWebEngineTester: +class JSWebEngineTester(JSTester): """Object returned by js_tester_webengine which provides a webview. @@ -189,25 +202,9 @@ class JSWebEngineTester: """ def __init__(self, webview, callback_checker, qtbot): - self.webview = webview + JSTester.__init__(self, webview, qtbot) self.webview.setPage(TestWebEnginePage(self.webview)) self.callback_checker = callback_checker - self._qtbot = qtbot - loader = jinja2.FileSystemLoader(os.path.dirname(__file__)) - self._jinja_env = jinja2.Environment(loader=loader, autoescape=True) - - def load(self, path, **kwargs): - """Load and display the given jinja test data. - - Args: - path: The path to the test file, relative to the javascript/ - folder. - **kwargs: Passed to jinja's template.render(). - """ - template = self._jinja_env.get_template(path) - with self._qtbot.waitSignal(self.webview.loadFinished) as blocker: - self.webview.setHtml(template.render(**kwargs)) - assert blocker.args == [True] def load_file(self, path: str, force=False): """Load a file from disk. @@ -262,7 +259,7 @@ class JSWebEngineTester: @pytest.fixture def js_tester(webview, qtbot): """Fixture to test javascript snippets in webkit.""" - return JSTester(webview, qtbot) + return JSWebKitTester(webview, qtbot) @pytest.fixture diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index 4a6a06c2c..179240d42 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -105,6 +105,7 @@ def test_set_xml(stylesheet_tester): stylesheet_tester.check_set(GREEN_BODY_BG) stylesheet_tester.check_eq("\"html\"", "document.documentElement.nodeName") + def test_set_svg(stylesheet_tester): """Test stylesheet is applied for svg files.""" stylesheet_tester.init_stylesheet() From 5913552dfec4f6e1a812a558cb54b3ca386de1f3 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Mon, 13 Nov 2017 19:57:11 -0500 Subject: [PATCH 12/16] Fix style issues in stylesheet tests --- tests/helpers/fixtures.py | 4 +- tests/helpers/utils.py | 3 +- tests/unit/javascript/conftest.py | 76 ++++++++++--------- .../position_caret/test_position_caret.py | 4 +- .../javascript/stylesheet/test_stylesheet.py | 15 ++-- 5 files changed, 55 insertions(+), 47 deletions(-) diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index 8caf6fcf5..6e17ebd71 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -37,7 +37,7 @@ import pytest import py.path # pylint: disable=no-name-in-module import helpers.stubs as stubsmod -from helpers.utils import CallbackChecker +import helpers.utils from qutebrowser.config import config, configdata, configtypes, configexc from qutebrowser.utils import objreg, standarddir from qutebrowser.browser.webkit import cookies @@ -81,7 +81,7 @@ class WinRegistryHelper: @pytest.fixture def callback_checker(qtbot): - return CallbackChecker(qtbot) + return helpers.utils.CallbackChecker(qtbot) class FakeStatusBar(QWidget): diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py index 45141f7dc..82c07fbd2 100644 --- a/tests/helpers/utils.py +++ b/tests/helpers/utils.py @@ -27,9 +27,10 @@ import contextlib import pytest -from qutebrowser.utils import qtutils from PyQt5.QtCore import QObject, pyqtSignal +from qutebrowser.utils import qtutils + qt58 = pytest.mark.skipif( qtutils.version_check('5.9'), reason="Needs Qt 5.8 or earlier") diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py index bcfc6b47a..7290d4d00 100644 --- a/tests/unit/javascript/conftest.py +++ b/tests/unit/javascript/conftest.py @@ -25,9 +25,12 @@ import logging import pytest import jinja2 -from helpers.utils import CallbackChecker + from PyQt5.QtCore import QUrl -from qutebrowser.utils.debug import qenum_key + +import helpers.utils +import qutebrowser.utils.debug +from qutebrowser.utils import utils try: from PyQt5.QtWebKit import QWebSettings @@ -46,8 +49,6 @@ except ImportError: QWebEngineSettings = None QWebEngineScript = None -from qutebrowser.utils import utils - if QWebPage is None: TestWebPage = None @@ -110,10 +111,12 @@ else: def javaScriptConsoleMessage(self, level, msg, line, source): """Fail tests on js console messages as they're used for errors.""" pytest.fail("[{}] js console ({}:{}): {}".format( - qenum_key(QWebEnginePage, level), source, line, msg)) + qutebrowser.utils.debug.qenum_key( + QWebEnginePage, level), source, line, msg)) class JSTester: + """Common subclass providing basic functionality for all JS testers. Attributes: @@ -141,6 +144,28 @@ class JSTester: self.webview.setHtml(template.render(**kwargs)) assert blocker.args == [True] + def load_file(self, path: str, force: bool = False): + """Load a file from disk. + + Args: + path: The string path from disk to load (relative to this file) + force: Whether to force loading even if the file is invalid. + """ + self.load_url(QUrl.fromLocalFile( + os.path.join(os.path.dirname(__file__), path)), force) + + def load_url(self, url: QUrl, force: bool = False): + """Load a given QUrl. + + Args: + url: The QUrl to load. + force: Whether to force loading even if the file is invalid. + """ + with self._qtbot.waitSignal(self.webview.loadFinished) as blocker: + self.webview.load(url) + if not force: + assert blocker.args == [True] + class JSWebKitTester(JSTester): @@ -153,7 +178,7 @@ class JSWebKitTester(JSTester): """ def __init__(self, webview, qtbot): - JSTester.__init__(self, webview, qtbot) + super().__init__(webview, qtbot) self.webview.setPage(TestWebPage(self.webview)) def scroll_anchor(self, name): @@ -201,32 +226,9 @@ class JSWebEngineTester(JSTester): _jinja_env: The jinja2 environment used to get templates. """ - def __init__(self, webview, callback_checker, qtbot): - JSTester.__init__(self, webview, qtbot) + def __init__(self, webview, qtbot): + super().__init__(webview, qtbot) self.webview.setPage(TestWebEnginePage(self.webview)) - self.callback_checker = callback_checker - - def load_file(self, path: str, force=False): - """Load a file from disk. - - Args: - path: The string path from disk to load (relative to this file) - force: Whether to force loading even if the file is invalid. - """ - self.load_url(QUrl.fromLocalFile( - os.path.join(os.path.dirname(__file__), path)), force) - - def load_url(self, url: QUrl, force: bool = False): - """Load a given QUrl. - - Args: - url: The QUrl to load. - force: Whether to force loading even if the file is invalid. - """ - with self._qtbot.waitSignal(self.webview.loadFinished) as blocker: - self.webview.load(url) - if not force: - assert blocker.args == [True] def run_file(self, filename: str, expected) -> None: """Run a javascript file. @@ -239,8 +241,7 @@ class JSWebEngineTester(JSTester): source = utils.read_file(os.path.join('javascript', filename)) self.run(source, expected) - def run(self, source: str, expected, - world=QWebEngineScript.ApplicationWorld) -> None: + def run(self, source: str, expected, world=None) -> None: """Run the given javascript source. Args: @@ -248,7 +249,10 @@ class JSWebEngineTester(JSTester): expected: The value expected return from the javascript execution world: The scope the javascript will run in """ - callback_checker = CallbackChecker(self._qtbot) + if world is None: + world = QWebEngineScript.ApplicationWorld + + callback_checker = helpers.utils.CallbackChecker(self._qtbot) assert self.webview.settings().testAttribute( QWebEngineSettings.JavascriptEnabled) self.webview.page().runJavaScript(source, world, @@ -257,7 +261,7 @@ class JSWebEngineTester(JSTester): @pytest.fixture -def js_tester(webview, qtbot): +def js_tester_webkit(webview, qtbot): """Fixture to test javascript snippets in webkit.""" return JSWebKitTester(webview, qtbot) @@ -265,4 +269,4 @@ def js_tester(webview, qtbot): @pytest.fixture def js_tester_webengine(callback_checker, webengineview, qtbot): """Fixture to test javascript snippets in webengine.""" - return JSWebEngineTester(webengineview, callback_checker, qtbot) + return JSWebEngineTester(webengineview, qtbot) diff --git a/tests/unit/javascript/position_caret/test_position_caret.py b/tests/unit/javascript/position_caret/test_position_caret.py index 7be62e3cc..fcfa5cf5d 100644 --- a/tests/unit/javascript/position_caret/test_position_caret.py +++ b/tests/unit/javascript/position_caret/test_position_caret.py @@ -65,9 +65,9 @@ class CaretTester: @pytest.fixture -def caret_tester(js_tester): +def caret_tester(js_tester_webkit): """Helper fixture to test caret browsing positions.""" - caret_tester = CaretTester(js_tester) + caret_tester = CaretTester(js_tester_webkit) # Showing webview here is necessary for test_scrolled_down_img to # succeed in some cases, see #1988 caret_tester.js.webview.show() diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index 179240d42..f74c04b8e 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -21,8 +21,10 @@ import os import pytest -from qutebrowser.utils import javascript + from PyQt5.QtWebEngineWidgets import QWebEngineProfile + +from qutebrowser.utils import javascript from qutebrowser.browser.webengine import webenginesettings @@ -61,12 +63,13 @@ class StylesheetTester: document_element="document.body"): """Check whether the css in ELEMENT is set to VALUE.""" self.js.run("window.getComputedStyle({}, null)" - ".getPropertyValue('{}');".format(document_element, - css_style), value) + ".getPropertyValue('{}');" + .format(document_element, + javascript.string_escape(css_style)), value) def check_eq(self, one, two, true=True): """Check if one and two are equal.""" - self.js.run("{} === {}".format(one, two), true) + self.js.run("{} === {};".format(one, two), true) @pytest.fixture @@ -103,7 +106,7 @@ def test_set_xml(stylesheet_tester): stylesheet_tester.init_stylesheet() stylesheet_tester.js.load_file('stylesheet/simple.xml') stylesheet_tester.check_set(GREEN_BODY_BG) - stylesheet_tester.check_eq("\"html\"", "document.documentElement.nodeName") + stylesheet_tester.check_eq('"html"', "document.documentElement.nodeName") def test_set_svg(stylesheet_tester): @@ -112,7 +115,7 @@ def test_set_svg(stylesheet_tester): stylesheet_tester.js.load_file('../../../misc/cheatsheet.svg') stylesheet_tester.check_set(GREEN_BODY_BG, document_element="document.documentElement") - stylesheet_tester.check_eq("\"svg\"", "document.documentElement.nodeName") + stylesheet_tester.check_eq('"svg"', "document.documentElement.nodeName") def test_set_error(stylesheet_tester): From 28572ce3b1d755e91d648c1cfae0df8e5b966acb Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Mon, 13 Nov 2017 22:08:06 -0500 Subject: [PATCH 13/16] Fix stylesheet tests crashing when no QtWebEngine available --- .../unit/javascript/stylesheet/test_stylesheet.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index f74c04b8e..83c9cbf98 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -22,10 +22,18 @@ import os import pytest -from PyQt5.QtWebEngineWidgets import QWebEngineProfile +try: + from PyQt5.QtWebEngineWidgets import QWebEngineProfile +except ImportError: + QWebEngineProfile = None + from qutebrowser.utils import javascript -from qutebrowser.browser.webengine import webenginesettings + +try: + from qutebrowser.browser.webengine import webenginesettings +except ImportError: + webenginesettings = None DEFAULT_BODY_BG = "rgba(0, 0, 0, 0)" @@ -51,6 +59,8 @@ class StylesheetTester: """Initialize the stylesheet with a provided css file.""" css_path = os.path.join(os.path.dirname(__file__), css_file) self.config_stub.val.content.user_stylesheets = css_path + if QWebEngineProfile is None: + pytest.skip("QTWebEngine not found.") p = QWebEngineProfile.defaultProfile() webenginesettings._init_stylesheet(p) From 92a6e61b5226ed25a6d133920c4b9f0e4418bc47 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Wed, 15 Nov 2017 02:05:34 -0500 Subject: [PATCH 14/16] Use importorskip to skip stylesheet tests on webkit only systems --- tests/unit/javascript/stylesheet/test_stylesheet.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index 83c9cbf98..7070cfceb 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -22,11 +22,8 @@ import os import pytest -try: - from PyQt5.QtWebEngineWidgets import QWebEngineProfile -except ImportError: - QWebEngineProfile = None - +QtWebEngineWidgets = pytest.importorskip("PyQt5.QtWebEngineWidgets") +QWebEngineProfile = QtWebEngineWidgets.QWebEngineProfile from qutebrowser.utils import javascript @@ -59,8 +56,6 @@ class StylesheetTester: """Initialize the stylesheet with a provided css file.""" css_path = os.path.join(os.path.dirname(__file__), css_file) self.config_stub.val.content.user_stylesheets = css_path - if QWebEngineProfile is None: - pytest.skip("QTWebEngine not found.") p = QWebEngineProfile.defaultProfile() webenginesettings._init_stylesheet(p) From 6c241f96ed626f600911cac77f357ba8eb131e0c Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Fri, 17 Nov 2017 17:49:15 -0500 Subject: [PATCH 15/16] Add test for appendChild #2723 Does some of #3295 --- .../javascript/stylesheet/test_appendchild.js | 47 +++++++++++++++++++ .../javascript/stylesheet/test_stylesheet.py | 8 ++++ 2 files changed, 55 insertions(+) create mode 100644 tests/unit/javascript/stylesheet/test_appendchild.js diff --git a/tests/unit/javascript/stylesheet/test_appendchild.js b/tests/unit/javascript/stylesheet/test_appendchild.js new file mode 100644 index 000000000..d1deadba6 --- /dev/null +++ b/tests/unit/javascript/stylesheet/test_appendchild.js @@ -0,0 +1,47 @@ +// Taken from acid3 bucket 5 +// https://github.com/w3c/web-platform-tests/blob/37cf5607a39357a0f213ab5df2e6b30499b0226f/acid/acid3/test.html#L2320 + +// test 65: bring in a couple of SVG files and some HTML files dynamically - preparation for later tests in this bucket +// NOTE FROM 2011 UPDATE: The svg.xml file still contains the SVG font, but it is no longer used +kungFuDeathGrip = document.createElement('p'); +kungFuDeathGrip.className = 'removed'; +var iframe, object; +// svg iframe +iframe = document.createElement('iframe'); +iframe.onload = function () { kungFuDeathGrip.title += '1' }; +iframe.src = "svg.xml"; +kungFuDeathGrip.appendChild(iframe); +// object iframe +object = document.createElement('object'); +object.onload = function () { kungFuDeathGrip.title += '2' }; +object.data = "svg.xml"; +kungFuDeathGrip.appendChild(object); +// xml iframe +iframe = document.createElement('iframe'); +iframe.onload = function () { kungFuDeathGrip.title += '3' }; +iframe.src = "empty.xml"; +kungFuDeathGrip.appendChild(iframe); +// html iframe +iframe = document.createElement('iframe'); +iframe.onload = function () { kungFuDeathGrip.title += '4' }; +iframe.src = "empty.html"; +kungFuDeathGrip.appendChild(iframe); +// html iframe +iframe = document.createElement('iframe'); +iframe.onload = function () { kungFuDeathGrip.title += '5' }; +iframe.src = "xhtml.1"; +kungFuDeathGrip.appendChild(iframe); +// html iframe +iframe = document.createElement('iframe'); +iframe.onload = function () { kungFuDeathGrip.title += '6' }; +iframe.src = "xhtml.2"; +kungFuDeathGrip.appendChild(iframe); +// html iframe +iframe = document.createElement('iframe'); +iframe.onload = function () { kungFuDeathGrip.title += '7' }; +iframe.src = "xhtml.3"; +kungFuDeathGrip.appendChild(iframe); +// add the lot to the document + +// Modified as we don't have a 'map' +document.getElementsByTagName('head')[0].appendChild(kungFuDeathGrip); diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet.py index 7070cfceb..47a181d77 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet.py @@ -128,3 +128,11 @@ def test_set_error(stylesheet_tester): stylesheet_tester.init_stylesheet() stylesheet_tester.js.load_file('non-existent.html', force=True) stylesheet_tester.check_set(GREEN_BODY_BG) + + +def test_appendchild(stylesheet_tester): + stylesheet_tester.init_stylesheet() + stylesheet_tester.js.load('stylesheet/simple.html') + js_test_file_path = \ + '../../tests/unit/javascript/stylesheet/test_appendchild.js' + stylesheet_tester.js.run_file(js_test_file_path, {}) From 633881039612e4123bebe3cf322afd5c5022021c Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Fri, 17 Nov 2017 21:42:24 -0500 Subject: [PATCH 16/16] Increase timeouts for javascript tests --- tests/unit/javascript/conftest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py index 7290d4d00..8490a5362 100644 --- a/tests/unit/javascript/conftest.py +++ b/tests/unit/javascript/conftest.py @@ -140,7 +140,8 @@ class JSTester: **kwargs: Passed to jinja's template.render(). """ template = self._jinja_env.get_template(path) - with self._qtbot.waitSignal(self.webview.loadFinished) as blocker: + with self._qtbot.waitSignal(self.webview.loadFinished, + timeout=2000) as blocker: self.webview.setHtml(template.render(**kwargs)) assert blocker.args == [True] @@ -161,7 +162,8 @@ class JSTester: url: The QUrl to load. force: Whether to force loading even if the file is invalid. """ - with self._qtbot.waitSignal(self.webview.loadFinished) as blocker: + with self._qtbot.waitSignal(self.webview.loadFinished, + timeout=2000) as blocker: self.webview.load(url) if not force: assert blocker.args == [True]