Initial attempt at using the tab API for tests/unit/javascript

This commit is contained in:
Florian Bruhin 2018-03-19 18:18:21 +01:00
parent e43f0a61b9
commit 460bd86579
6 changed files with 106 additions and 297 deletions

View File

@ -43,12 +43,24 @@ import helpers.stubs as stubsmod
import helpers.utils import helpers.utils
from qutebrowser.config import (config, configdata, configtypes, configexc, from qutebrowser.config import (config, configdata, configtypes, configexc,
configfiles) configfiles)
from qutebrowser.utils import objreg, standarddir from qutebrowser.utils import objreg, standarddir, utils
from qutebrowser.browser import greasemonkey
from qutebrowser.browser.webkit import cookies from qutebrowser.browser.webkit import cookies
from qutebrowser.misc import savemanager, sql from qutebrowser.misc import savemanager, sql
from qutebrowser.keyinput import modeman from qutebrowser.keyinput import modeman
try:
from PyQt5.QtWebKitWidgets import QWebView
except ImportError:
QWebView = None
try:
from PyQt5.QtWebEngineWidgets import QWebEngineView
except ImportError:
QWebEngineView = None
class WinRegistryHelper: class WinRegistryHelper:
"""Helper class for win_registry.""" """Helper class for win_registry."""
@ -143,6 +155,47 @@ def fake_web_tab(stubs, tab_registry, mode_manager, qapp):
return stubs.FakeWebTab return stubs.FakeWebTab
@pytest.fixture
def greasemonkey_manager(data_tmpdir):
gm_manager = greasemonkey.GreasemonkeyManager()
objreg.register('greasemonkey', gm_manager)
yield
objreg.delete('greasemonkey')
@pytest.fixture
def webkit_tab(qtbot, tab_registry, cookiejar_and_cache, mode_manager,
session_manager_stub, greasemonkey_manager):
webkittab = pytest.importorskip('qutebrowser.browser.webkit.webkittab')
tab = webkittab.WebKitTab(win_id=0, mode_manager=mode_manager,
private=False)
qtbot.add_widget(tab)
return tab
@pytest.fixture
def webengine_tab(qtbot, tab_registry, fake_args, mode_manager,
session_manager_stub, greasemonkey_manager,
redirect_webengine_data):
webenginetab = pytest.importorskip(
'qutebrowser.browser.webengine.webenginetab')
tab = webenginetab.WebEngineTab(win_id=0, mode_manager=mode_manager,
private=False)
qtbot.add_widget(tab)
return tab
@pytest.fixture(params=['webkit', 'webengine'])
def web_tab(request):
"""A WebKitTab/WebEngineTab."""
if request.param == 'webkit':
return request.getfixturevalue('webkit_tab')
elif request.param == 'webengine':
return request.getfixturevalue('webengine_tab')
else:
raise utils.Unreachable
def _generate_cmdline_tests(): def _generate_cmdline_tests():
"""Generate testcases for test_split_binding.""" """Generate testcases for test_split_binding."""
@attr.s @attr.s

View File

@ -472,6 +472,9 @@ class SessionManagerStub:
def list_sessions(self): def list_sessions(self):
return self.sessions return self.sessions
def save_autosave(self):
pass
class TabbedBrowserStub(QObject): class TabbedBrowserStub(QObject):

View File

@ -1,110 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 <http://www.gnu.org/licenses/>.
import pytest
from qutebrowser.browser import browsertab
from qutebrowser.utils import utils
pytestmark = pytest.mark.usefixtures('redirect_webengine_data')
try:
from PyQt5.QtWebKitWidgets import QWebView
except ImportError:
QWebView = None
try:
from PyQt5.QtWebEngineWidgets import QWebEngineView
except ImportError:
QWebEngineView = None
@pytest.fixture(params=[QWebView, QWebEngineView])
def view(qtbot, config_stub, request):
if request.param is None:
pytest.skip("View not available")
v = request.param()
qtbot.add_widget(v)
return v
@pytest.fixture(params=['webkit', 'webengine'])
def tab(request, qtbot, tab_registry, cookiejar_and_cache, mode_manager):
if request.param == 'webkit':
webkittab = pytest.importorskip('qutebrowser.browser.webkit.webkittab')
tab_class = webkittab.WebKitTab
elif request.param == 'webengine':
webenginetab = pytest.importorskip(
'qutebrowser.browser.webengine.webenginetab')
tab_class = webenginetab.WebEngineTab
else:
raise utils.Unreachable
t = tab_class(win_id=0, mode_manager=mode_manager)
qtbot.add_widget(t)
yield t
class Zoom(browsertab.AbstractZoom):
def _set_factor_internal(self, _factor):
pass
def factor(self):
raise utils.Unreachable
class Tab(browsertab.AbstractTab):
# pylint: disable=abstract-method
def __init__(self, win_id, mode_manager, parent=None):
super().__init__(win_id=win_id, mode_manager=mode_manager,
parent=parent)
self.history = browsertab.AbstractHistory(self)
self.scroller = browsertab.AbstractScroller(self, parent=self)
self.caret = browsertab.AbstractCaret(mode_manager=mode_manager,
tab=self, parent=self)
self.zoom = Zoom(tab=self)
self.search = browsertab.AbstractSearch(parent=self)
self.printing = browsertab.AbstractPrinting()
self.elements = browsertab.AbstractElements(tab=self)
self.action = browsertab.AbstractAction(tab=self)
def _install_event_filter(self):
pass
@pytest.mark.xfail(run=False, reason='Causes segfaults, see #1638')
def test_tab(qtbot, view, config_stub, tab_registry, mode_manager):
tab_w = Tab(win_id=0, mode_manager=mode_manager)
qtbot.add_widget(tab_w)
assert tab_w.win_id == 0
assert tab_w._widget is None
tab_w._set_widget(view)
assert tab_w._widget is view
assert tab_w.history._tab is tab_w
assert tab_w.history._history is view.history()
assert view.parent() is tab_w
with qtbot.waitExposed(tab_w):
tab_w.show()

View File

@ -27,91 +27,9 @@ import pytest
import jinja2 import jinja2
from PyQt5.QtCore import QUrl from PyQt5.QtCore import QUrl
try:
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtWebKitWidgets import QWebPage
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
import helpers.utils import helpers.utils
import qutebrowser.utils.debug from qutebrowser.utils import utils, usertypes
from qutebrowser.utils import utils
if QWebPage is None:
TestWebPage = None
else:
class TestWebPage(QWebPage):
"""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, msg, line, source):
"""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):
"""QWebEnginePage which overrides javascript logging 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(
qutebrowser.utils.debug.qenum_key(
QWebEnginePage, level), source, line, msg))
class JSTester: class JSTester:
@ -119,14 +37,14 @@ class JSTester:
"""Common subclass providing basic functionality for all JS testers. """Common subclass providing basic functionality for all JS testers.
Attributes: Attributes:
webview: The webview which is used. tab: The tab object which is used.
_qtbot: The QtBot fixture from pytest-qt. qtbot: The QtBot fixture from pytest-qt.
_jinja_env: The jinja2 environment used to get templates. _jinja_env: The jinja2 environment used to get templates.
""" """
def __init__(self, webview, qtbot): def __init__(self, tab, qtbot):
self.webview = webview self.tab = tab
self._qtbot = qtbot self.qtbot = qtbot
loader = jinja2.FileSystemLoader(os.path.dirname(__file__)) loader = jinja2.FileSystemLoader(os.path.dirname(__file__))
self._jinja_env = jinja2.Environment(loader=loader, autoescape=True) self._jinja_env = jinja2.Environment(loader=loader, autoescape=True)
@ -139,9 +57,9 @@ class JSTester:
**kwargs: Passed to jinja's template.render(). **kwargs: Passed to jinja's template.render().
""" """
template = self._jinja_env.get_template(path) template = self._jinja_env.get_template(path)
with self._qtbot.waitSignal(self.webview.loadFinished, with self.qtbot.waitSignal(self.tab.load_finished,
timeout=2000) as blocker: timeout=2000) as blocker:
self.webview.setHtml(template.render(**kwargs)) self.tab.set_html(template.render(**kwargs))
assert blocker.args == [True] assert blocker.args == [True]
def load_file(self, path: str, force: bool = False): def load_file(self, path: str, force: bool = False):
@ -161,77 +79,13 @@ class JSTester:
url: The QUrl to load. url: The QUrl to load.
force: Whether to force loading even if the file is invalid. force: Whether to force loading even if the file is invalid.
""" """
with self._qtbot.waitSignal(self.webview.loadFinished, with self.qtbot.waitSignal(self.tab.load_finished,
timeout=2000) as blocker: timeout=2000) as blocker:
self.webview.load(url) self.tab.openurl(url)
if not force: if not force:
assert blocker.args == [True] assert blocker.args == [True]
def run_file(self, filename: str, expected=None) -> None:
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):
super().__init__(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.
Args:
filename: The javascript filename, relative to
qutebrowser/javascript.
Return:
The javascript return value.
"""
source = utils.read_file(os.path.join('javascript', filename))
return self.run(source)
def run(self, source):
"""Run the given javascript source.
Args:
source: The source to run as a string.
Return:
The javascript return value.
"""
assert self.webview.settings().testAttribute(
QWebSettings.JavascriptEnabled)
return self.webview.page().mainFrame().evaluateJavaScript(source)
class JSWebEngineTester(JSTester):
"""Object returned by js_tester_webengine which provides 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):
super().__init__(webview, qtbot)
self.webview.setPage(TestWebEnginePage(self.webview))
def run_file(self, filename: str, expected) -> None:
"""Run a javascript file. """Run a javascript file.
Args: Args:
@ -250,24 +104,34 @@ class JSWebEngineTester(JSTester):
expected: The value expected return from the javascript execution expected: The value expected return from the javascript execution
world: The scope the javascript will run in world: The scope the javascript will run in
""" """
if world is None: callback_checker = helpers.utils.CallbackChecker(self.qtbot)
world = QWebEngineScript.ApplicationWorld self.tab.run_js_async(source, callback_checker.callback, world=world)
callback_checker = helpers.utils.CallbackChecker(self._qtbot)
assert self.webview.settings().testAttribute(
QWebEngineSettings.JavascriptEnabled)
self.webview.page().runJavaScript(source, world,
callback_checker.callback)
callback_checker.check(expected) callback_checker.check(expected)
def scroll_anchor(self, name):
"""Scroll the main frame to the given anchor."""
# FIXME This might be useful in the tab API?
assert self.tab.backend == usertypes.Backend.QtWebKit
page = self.tab._widget.page()
old_pos = page.mainFrame().scrollPosition()
page.mainFrame().scrollToAnchor(name)
new_pos = page.mainFrame().scrollPosition()
assert old_pos != new_pos
@pytest.fixture @pytest.fixture
def js_tester_webkit(webview, qtbot): def js_tester_webkit(webkit_tab, qtbot):
"""Fixture to test javascript snippets in webkit.""" """Fixture to test javascript snippets in webkit."""
return JSWebKitTester(webview, qtbot) return JSTester(webkit_tab, qtbot)
@pytest.fixture @pytest.fixture
def js_tester_webengine(callback_checker, webengineview, qtbot): def js_tester_webengine(webengine_tab, qtbot):
"""Fixture to test javascript snippets in webengine.""" """Fixture to test javascript snippets in webengine."""
return JSWebEngineTester(webengineview, qtbot) return JSTester(webengine_tab, qtbot)
@pytest.fixture
def js_tester(web_tab, qtbot):
"""Fixture to test javascript snippets with both backends."""
return JSTester(web_tab, qtbot)

View File

@ -21,8 +21,7 @@
import pytest import pytest
# FIXME:qtwebengine Make these tests use the tab API import helpers.utils
pytest.importorskip('PyQt5.QtWebKit')
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWebKit import QWebSettings from PyQt5.QtWebKit import QWebSettings
@ -53,24 +52,26 @@ class CaretTester:
def check(self): def check(self):
"""Check whether the caret is before the MARKER text.""" """Check whether the caret is before the MARKER text."""
self.js.run_file('position_caret.js') self.js.run_file('position_caret.js')
self.js.webview.triggerPageAction(QWebPage.SelectNextWord) self.js.tab.caret.toggle_selection()
assert self.js.webview.selectedText().rstrip() == "MARKER" self.js.tab.caret.move_to_next_word()
callback_checker = helpers.utils.CallbackChecker(self.js.qtbot)
self.js.tab.caret.selection(lambda text:
callback_checker.callback(text.rstrip()))
callback_checker.check('MARKER')
def check_scrolled(self): def check_scrolled(self):
"""Check if the page is scrolled down.""" """Check if the page is scrolled down."""
frame = self.js.webview.page().mainFrame() assert not self.js.tab.scroller.at_top()
minimum = frame.scrollBarMinimum(Qt.Vertical)
value = frame.scrollBarValue(Qt.Vertical)
assert value > minimum
@pytest.fixture @pytest.fixture
def caret_tester(js_tester_webkit): def caret_tester(js_tester):
"""Helper fixture to test caret browsing positions.""" """Helper fixture to test caret browsing positions."""
caret_tester = CaretTester(js_tester_webkit) caret_tester = CaretTester(js_tester)
# Showing webview here is necessary for test_scrolled_down_img to # Showing webview here is necessary for test_scrolled_down_img to
# succeed in some cases, see #1988 # succeed in some cases, see #1988
caret_tester.js.webview.show() caret_tester.js.tab.show()
return caret_tester return caret_tester

View File

@ -56,9 +56,7 @@ class StylesheetTester:
"""Initialize the stylesheet with a provided css file.""" """Initialize the stylesheet with a provided css file."""
css_path = os.path.join(os.path.dirname(__file__), css_file) css_path = os.path.join(os.path.dirname(__file__), css_file)
self.config_stub.val.content.user_stylesheets = css_path self.config_stub.val.content.user_stylesheets = css_path
profile = QWebEngineProfile.defaultProfile() self.js.tab._init_stylesheet()
setter = webenginesettings.ProfileSetter(profile)
setter.init_stylesheet()
def set_css(self, css): def set_css(self, css):
"""Set document style to `css` via stylesheet.js.""" """Set document style to `css` via stylesheet.js."""
@ -82,7 +80,7 @@ class StylesheetTester:
def stylesheet_tester(js_tester_webengine, config_stub): 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 = StylesheetTester(js_tester_webengine, config_stub)
ss_tester.js.webview.show() ss_tester.js.tab.show()
return ss_tester return ss_tester