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
from qutebrowser.config import (config, configdata, configtypes, configexc,
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.misc import savemanager, sql
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:
"""Helper class for win_registry."""
@ -143,6 +155,47 @@ def fake_web_tab(stubs, tab_registry, mode_manager, qapp):
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():
"""Generate testcases for test_split_binding."""
@attr.s

View File

@ -472,6 +472,9 @@ class SessionManagerStub:
def list_sessions(self):
return self.sessions
def save_autosave(self):
pass
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
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 qutebrowser.utils.debug
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))
from qutebrowser.utils import utils, usertypes
class JSTester:
@ -119,14 +37,14 @@ class JSTester:
"""Common subclass providing basic functionality for all JS testers.
Attributes:
webview: The webview which is used.
_qtbot: The QtBot fixture from pytest-qt.
tab: The tab object which is used.
qtbot: The QtBot fixture from pytest-qt.
_jinja_env: The jinja2 environment used to get templates.
"""
def __init__(self, webview, qtbot):
self.webview = webview
self._qtbot = qtbot
def __init__(self, tab, qtbot):
self.tab = tab
self.qtbot = qtbot
loader = jinja2.FileSystemLoader(os.path.dirname(__file__))
self._jinja_env = jinja2.Environment(loader=loader, autoescape=True)
@ -139,9 +57,9 @@ class JSTester:
**kwargs: Passed to jinja's template.render().
"""
template = self._jinja_env.get_template(path)
with self._qtbot.waitSignal(self.webview.loadFinished,
timeout=2000) as blocker:
self.webview.setHtml(template.render(**kwargs))
with self.qtbot.waitSignal(self.tab.load_finished,
timeout=2000) as blocker:
self.tab.set_html(template.render(**kwargs))
assert blocker.args == [True]
def load_file(self, path: str, force: bool = False):
@ -161,77 +79,13 @@ 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,
timeout=2000) as blocker:
self.webview.load(url)
with self.qtbot.waitSignal(self.tab.load_finished,
timeout=2000) as blocker:
self.tab.openurl(url)
if not force:
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):
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:
def run_file(self, filename: str, expected=None) -> None:
"""Run a javascript file.
Args:
@ -250,24 +104,34 @@ class JSWebEngineTester(JSTester):
expected: The value expected return from the javascript execution
world: The scope the javascript will run in
"""
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,
callback_checker.callback)
callback_checker = helpers.utils.CallbackChecker(self.qtbot)
self.tab.run_js_async(source, callback_checker.callback, world=world)
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
def js_tester_webkit(webview, qtbot):
def js_tester_webkit(webkit_tab, qtbot):
"""Fixture to test javascript snippets in webkit."""
return JSWebKitTester(webview, qtbot)
return JSTester(webkit_tab, qtbot)
@pytest.fixture
def js_tester_webengine(callback_checker, webengineview, qtbot):
def js_tester_webengine(webengine_tab, qtbot):
"""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
# FIXME:qtwebengine Make these tests use the tab API
pytest.importorskip('PyQt5.QtWebKit')
import helpers.utils
from PyQt5.QtCore import Qt
from PyQt5.QtWebKit import QWebSettings
@ -53,24 +52,26 @@ class CaretTester:
def check(self):
"""Check whether the caret is before the MARKER text."""
self.js.run_file('position_caret.js')
self.js.webview.triggerPageAction(QWebPage.SelectNextWord)
assert self.js.webview.selectedText().rstrip() == "MARKER"
self.js.tab.caret.toggle_selection()
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):
"""Check if the page is scrolled down."""
frame = self.js.webview.page().mainFrame()
minimum = frame.scrollBarMinimum(Qt.Vertical)
value = frame.scrollBarValue(Qt.Vertical)
assert value > minimum
assert not self.js.tab.scroller.at_top()
@pytest.fixture
def caret_tester(js_tester_webkit):
def caret_tester(js_tester):
"""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
# succeed in some cases, see #1988
caret_tester.js.webview.show()
caret_tester.js.tab.show()
return caret_tester

View File

@ -56,9 +56,7 @@ 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
profile = QWebEngineProfile.defaultProfile()
setter = webenginesettings.ProfileSetter(profile)
setter.init_stylesheet()
self.js.tab._init_stylesheet()
def set_css(self, css):
"""Set document style to `css` via stylesheet.js."""
@ -82,7 +80,7 @@ class StylesheetTester:
def stylesheet_tester(js_tester_webengine, config_stub):
"""Helper fixture to test stylesheets."""
ss_tester = StylesheetTester(js_tester_webengine, config_stub)
ss_tester.js.webview.show()
ss_tester.js.tab.show()
return ss_tester