diff --git a/.travis.yml b/.travis.yml index 449230232..3812ca614 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,9 @@ matrix: - os: linux env: DOCKER=archlinux services: docker + - os: linux + env: DOCKER=archlinux QUTE_BDD_WEBENGINE=true + services: docker - os: linux env: DOCKER=ubuntu-xenial services: docker diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 07a23092e..5dfaa0086 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -107,6 +107,8 @@ Changed - Lots of improvements to and bugfixes for the QtWebEngine backend, such as working hints. However, using qutebrowser directly from git is still advised when using `--backend webengine`. +- `content -> javascript-can-open-windows` got renamed to + `javascript-can-open-windows-automatically`. Deprecated ~~~~~~~~~~ diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index f3a6e29f5..6f4d58868 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -157,7 +157,7 @@ |<>|Enable or disable hyperlink auditing (). |<>|Allow websites to request geolocations. |<>|Allow websites to show notifications. -|<>|Whether JavaScript programs can open new windows. +|<>|Whether JavaScript programs can open new windows without user interaction. |<>|Whether JavaScript programs can close windows. |<>|Whether JavaScript programs can read or write to the clipboard. |<>|Whether all javascript prompts should be ignored. @@ -1427,9 +1427,9 @@ Valid values: Default: +pass:[ask]+ -[[content-javascript-can-open-windows]] -=== javascript-can-open-windows -Whether JavaScript programs can open new windows. +[[content-javascript-can-open-windows-automatically]] +=== javascript-can-open-windows-automatically +Whether JavaScript programs can open new windows without user interaction. Valid values: diff --git a/pytest.ini b/pytest.ini index b71cc3e75..0d5b86a4c 100644 --- a/pytest.ini +++ b/pytest.ini @@ -17,6 +17,8 @@ markers = flaky_once: Try to rerun this test once if it fails qtwebengine_todo: Features still missing with QtWebEngine qtwebengine_skip: Tests not applicable with QtWebEngine + qtwebkit_skip: Tests not applicable with QtWebKit + qtwebengine_createWindow: Tests using createWindow with QtWebEngine (QTBUG-54419) qt_log_level_fail = WARNING qt_log_ignore = ^SpellCheck: .* diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 5c93bae49..a8ca17149 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -112,7 +112,7 @@ MAPPINGS = { Attribute(QWebEngineSettings.AutoLoadImages), 'allow-javascript': Attribute(QWebEngineSettings.JavascriptEnabled), - 'javascript-can-open-windows': + 'javascript-can-open-windows-automatically': Attribute(QWebEngineSettings.JavascriptCanOpenWindows), 'javascript-can-access-clipboard': Attribute(QWebEngineSettings.JavascriptCanAccessClipboard), diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 41ddc6c80..97f810e67 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -381,7 +381,7 @@ class WebEngineTab(browsertab.AbstractTab): def __init__(self, win_id, mode_manager, parent=None): super().__init__(win_id) - widget = webview.WebEngineView(tabdata=self.data) + widget = webview.WebEngineView(tabdata=self.data, win_id=win_id) self.history = WebEngineHistory(self) self.scroller = WebEngineScroller(self, parent=self) self.caret = WebEngineCaret(win_id=win_id, mode_manager=mode_manager, @@ -406,9 +406,9 @@ class WebEngineTab(browsertab.AbstractTab): ]) script = QWebEngineScript() script.setInjectionPoint(QWebEngineScript.DocumentCreation) - page = self._widget.page() script.setSourceCode(js_code) + page = self._widget.page() try: page.runJavaScript("", QWebEngineScript.ApplicationWorld) except TypeError: @@ -504,6 +504,11 @@ class WebEngineTab(browsertab.AbstractTab): if title == title_url.toDisplayString(QUrl.RemoveScheme).strip('/'): title = "" + # Don't add history entry if the URL is invalid anyways + if not url.isValid(): + log.misc.debug("Ignoring invalid URL being added to history") + return + self.add_history_item.emit(url, requested_url, title) def _connect_signals(self): diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 451a493a0..daa90f85a 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -19,6 +19,7 @@ """The main browser widget for QtWebEngine.""" +import os from PyQt5.QtCore import pyqtSignal, QUrl # pylint: disable=no-name-in-module,import-error,useless-suppression @@ -26,17 +27,73 @@ from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage # pylint: enable=no-name-in-module,import-error,useless-suppression from qutebrowser.config import config -from qutebrowser.utils import log, debug, usertypes +from qutebrowser.utils import log, debug, usertypes, objreg, qtutils, message class WebEngineView(QWebEngineView): """Custom QWebEngineView subclass with qutebrowser-specific features.""" - def __init__(self, tabdata, parent=None): + def __init__(self, tabdata, win_id, parent=None): super().__init__(parent) + self._win_id = win_id self.setPage(WebEnginePage(tabdata, parent=self)) + def createWindow(self, wintype): + """Called by Qt when a page wants to create a new window. + + This function is called from the createWindow() method of the + associated QWebEnginePage, each time the page wants to create a new + window of the given type. This might be the result, for example, of a + JavaScript request to open a document in a new window. + + Args: + wintype: This enum describes the types of window that can be + created by the createWindow() function. + + QWebEnginePage::WebBrowserWindow: + A complete web browser window. + QWebEnginePage::WebBrowserTab: + A web browser tab. + QWebEnginePage::WebDialog: + A window without decoration. + QWebEnginePage::WebBrowserBackgroundTab: + A web browser tab without hiding the current visible + WebEngineView. (Added in Qt 5.7) + + Return: + The new QWebEngineView object. + """ + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-54419 + vercheck = qtutils.version_check + qtbug_54419_fixed = ((vercheck('5.6.2') and not vercheck('5.7.0')) or + qtutils.version_check('5.7.1') or + os.environ.get('QUTE_QTBUG54419_PATCHED', '')) + if not qtbug_54419_fixed: + message.error(self._win_id, "Qt 5.6.2/5.7.1 or newer is required " + "to open new tabs via JS!") + return None + + debug_type = debug.qenum_key(QWebEnginePage, wintype) + log.webview.debug("createWindow with type {}".format(debug_type)) + background = False + if wintype in [QWebEnginePage.WebBrowserWindow, + QWebEnginePage.WebDialog]: + log.webview.warning("{} requested, but we don't support " + "that!".format(debug_type)) + elif wintype == QWebEnginePage.WebBrowserTab: + pass + elif (hasattr(QWebEnginePage, 'WebBrowserBackgroundTab') and + wintype == QWebEnginePage.WebBrowserBackgroundTab): + background = True + else: + raise ValueError("Invalid wintype {}".format(debug_type)) + + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window=self._win_id) + # pylint: disable=protected-access + return tabbed_browser.tabopen(background=background)._widget + class WebEnginePage(QWebEnginePage): @@ -74,11 +131,6 @@ class WebEnginePage(QWebEnginePage): logger = level_to_logger[level] logger(logstring) - def createWindow(self, _typ): - """Handle new windows via JS.""" - log.stub() - return None - def acceptNavigationRequest(self, url: QUrl, typ: QWebEnginePage.NavigationType, diff --git a/qutebrowser/browser/webkit/webkitsettings.py b/qutebrowser/browser/webkit/webkitsettings.py index d5af1dfd9..911a9709b 100644 --- a/qutebrowser/browser/webkit/webkitsettings.py +++ b/qutebrowser/browser/webkit/webkitsettings.py @@ -118,7 +118,7 @@ MAPPINGS = { Attribute(QWebSettings.AutoLoadImages), 'allow-javascript': Attribute(QWebSettings.JavascriptEnabled), - 'javascript-can-open-windows': + 'javascript-can-open-windows-automatically': Attribute(QWebSettings.JavascriptCanOpenWindows), 'javascript-can-close-windows': Attribute(QWebSettings.JavascriptCanCloseWindows), diff --git a/qutebrowser/browser/webkit/webview.py b/qutebrowser/browser/webkit/webview.py index 52e75ad14..f9d22c9d6 100644 --- a/qutebrowser/browser/webkit/webview.py +++ b/qutebrowser/browser/webkit/webview.py @@ -29,7 +29,7 @@ from PyQt5.QtWebKitWidgets import QWebView, QWebPage, QWebFrame from qutebrowser.config import config from qutebrowser.keyinput import modeman -from qutebrowser.utils import log, usertypes, utils, qtutils, objreg +from qutebrowser.utils import log, usertypes, utils, qtutils, objreg, debug from qutebrowser.browser.webkit import webpage, webkitelem @@ -218,6 +218,8 @@ class WebView(QWebView): Return: The new QWebView object. """ + debug_type = debug.qenum_key(QWebPage, wintype) + log.webview.debug("createWindow with type {}".format(debug_type)) if wintype == QWebPage.WebModalDialog: log.webview.warning("WebModalDialog requested, but we don't " "support that!") diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 636e3a4d0..e6aa3db7c 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -385,6 +385,8 @@ class ConfigManager(QObject): ('completion', 'history-length'): 'cmd-history-max-items', ('colors', 'downloads.fg'): 'downloads.fg.start', ('ui', 'show-keyhints'): 'keyhint-blacklist', + ('content', 'javascript-can-open-windows'): + 'javascript-can-open-windows-automatically', } DELETED_OPTIONS = [ ('colors', 'tab.separator'), diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 89aee870f..533b354f8 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -808,9 +808,10 @@ def data(readonly=False): SettingValue(typ.BoolAsk(), 'ask'), "Allow websites to show notifications."), - ('javascript-can-open-windows', + ('javascript-can-open-windows-automatically', SettingValue(typ.Bool(), 'false'), - "Whether JavaScript programs can open new windows."), + "Whether JavaScript programs can open new windows without user " + "interaction."), ('javascript-can-close-windows', SettingValue(typ.Bool(), 'false', diff --git a/qutebrowser/misc/crashdialog.py b/qutebrowser/misc/crashdialog.py index d22415b7b..8d44da041 100644 --- a/qutebrowser/misc/crashdialog.py +++ b/qutebrowser/misc/crashdialog.py @@ -99,7 +99,7 @@ def get_fatal_crash_dialog(debug, data): def _get_environment_vars(): """Gather environment variables for the crash info.""" masks = ('DESKTOP_SESSION', 'DE', 'QT_*', 'PYTHON*', 'LC_*', 'LANG', - 'XDG_*') + 'XDG_*', 'QUTE_*') info = [] for key, value in os.environ.items(): for m in masks: diff --git a/scripts/dev/ci/travis_run.sh b/scripts/dev/ci/travis_run.sh index 1cb8d2981..5b42d6057 100644 --- a/scripts/dev/ci/travis_run.sh +++ b/scripts/dev/ci/travis_run.sh @@ -1,7 +1,7 @@ #!/bin/bash if [[ $DOCKER ]]; then - docker run --privileged -v $PWD:/outside qutebrowser/travis:$DOCKER + docker run --privileged -v $PWD:/outside -e QUTE_BDD_WEBENGINE=$QUTE_BDD_WEBENGINE qutebrowser/travis:$DOCKER else args=() [[ $TESTENV == docs ]] && args=('--no-authors') diff --git a/tests/conftest.py b/tests/conftest.py index b04698dbe..71018fd1d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,11 +21,9 @@ """The qutebrowser test suite conftest file.""" -import re import os import sys import warnings -import operator import pytest import hypothesis @@ -37,10 +35,6 @@ from helpers.logfail import fail_on_logging from helpers.messagemock import message_mock from helpers.fixtures import * # pylint: disable=wildcard-import -from PyQt5.QtCore import PYQT_VERSION - -from qutebrowser.utils import qtutils - # Set hypothesis settings hypothesis.settings.register_profile('default', @@ -78,7 +72,7 @@ def _apply_platform_markers(item): item.add_marker(skipif_marker) -def pytest_collection_modifyitems(items): +def pytest_collection_modifyitems(config, items): """Handle custom markers. pytest hook called after collection has been performed. @@ -101,7 +95,12 @@ def pytest_collection_modifyitems(items): Reference: http://pytest.org/latest/plugins.html """ + remaining_items = [] + deselected_items = [] + for item in items: + deselected = False + if 'qapp' in getattr(item, 'fixturenames', ()): item.add_marker('gui') @@ -110,9 +109,13 @@ def pytest_collection_modifyitems(items): item.module.__file__, os.path.commonprefix([__file__, item.module.__file__])) - module_root_dir = os.path.split(module_path)[0] + module_root_dir = module_path.split(os.sep)[0] + assert module_root_dir in ['end2end', 'unit', 'helpers', + 'test_conftest.py'] if module_root_dir == 'end2end': item.add_marker(pytest.mark.end2end) + elif os.environ.get('QUTE_BDD_WEBENGINE', ''): + deselected = True _apply_platform_markers(item) if item.get_marker('xfail_norun'): @@ -120,6 +123,14 @@ def pytest_collection_modifyitems(items): if item.get_marker('flaky_once'): item.add_marker(pytest.mark.flaky(reruns=1)) + if deselected: + deselected_items.append(item) + else: + remaining_items.append(item) + + config.hook.pytest_deselected(items=deselected_items) + items[:] = remaining_items + def pytest_ignore_collect(path): """Ignore BDD tests if we're unable to run them.""" @@ -144,6 +155,17 @@ def pytest_addoption(parser): help='Use QtWebEngine for BDD tests') +def pytest_configure(config): + webengine_arg = config.getoption('--qute-bdd-webengine') + webengine_env = os.environ.get('QUTE_BDD_WEBENGINE', '') + config.webengine = bool(webengine_arg or webengine_env) + # Fail early if QtWebEngine is not available + # pylint: disable=no-name-in-module,unused-variable,useless-suppression + if config.webengine: + import PyQt5.QtWebEngineWidgets + # pylint: enable=no-name-in-module,unused-variable,useless-suppression + + @pytest.fixture(scope='session', autouse=True) def check_display(request): if (not request.config.getoption('--no-xvfb') and @@ -182,71 +204,3 @@ def pytest_sessionfinish(exitstatus): status_file = os.path.join(cache_dir, 'pytest_status') with open(status_file, 'w', encoding='ascii') as f: f.write(str(exitstatus)) - - -if not getattr(sys, 'frozen', False): - def _get_version_tag(tag): - """Handle tags like pyqt>=5.3.1 for BDD tests. - - This transforms e.g. pyqt>=5.3.1 into an appropriate @pytest.mark.skip - marker, and falls back to pytest-bdd's implementation for all other - casesinto an appropriate @pytest.mark.skip marker, and falls back to - """ - version_re = re.compile(r""" - (?Pqt|pyqt) - (?P==|>|>=|<|<=|!=) - (?P\d+\.\d+\.\d+) - """, re.VERBOSE) - - match = version_re.match(tag) - if not match: - return None - - operators = { - '==': operator.eq, - '>': operator.gt, - '<': operator.lt, - '>=': operator.ge, - '<=': operator.le, - '!=': operator.ne, - } - - package = match.group('package') - op = operators[match.group('operator')] - version = match.group('version') - - if package == 'qt': - return pytest.mark.skipif(qtutils.version_check(version, op), - reason='Needs ' + tag) - elif package == 'pyqt': - major, minor, patch = [int(e) for e in version.split('.')] - hex_version = (major << 16) | (minor << 8) | patch - return pytest.mark.skipif(not op(PYQT_VERSION, hex_version), - reason='Needs ' + tag) - else: - raise ValueError("Invalid package {!r}".format(package)) - - def _get_qtwebengine_tag(tag): - """Handle a @qtwebengine_* tag.""" - pytest_marks = { - 'qtwebengine_todo': pytest.mark.qtwebengine_todo, - 'qtwebengine_skip': pytest.mark.qtwebengine_skip, - } - if not any(tag.startswith(t + ':') for t in pytest_marks): - return None - name, desc = tag.split(':', maxsplit=1) - return pytest_marks[name](desc) - - def pytest_bdd_apply_tag(tag, function): - """Handle custom tags for BDD tests. - - This tries various functions, and if none knows how to handle this tag, - it returns None so it falls back to pytest-bdd's implementation. - """ - funcs = [_get_version_tag, _get_qtwebengine_tag] - for func in funcs: - mark = func(tag) - if mark is not None: - mark(function) - return True - return None diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index 73d4a87ab..09ca18f28 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -21,11 +21,16 @@ """Things needed for end2end testing.""" +import re import os +import sys import shutil import pstats import os.path +import operator + import pytest +from PyQt5.QtCore import PYQT_VERSION pytest.register_assert_rewrite('end2end.fixtures') @@ -33,6 +38,7 @@ from end2end.fixtures.webserver import httpbin, httpbin_after_test, ssl_server from end2end.fixtures.quteprocess import (quteproc_process, quteproc, quteproc_new) from end2end.fixtures.testprocess import pytest_runtest_makereport +from qutebrowser.utils import qtutils def pytest_configure(config): @@ -51,3 +57,104 @@ def pytest_unconfigure(config): for fn in os.listdir('prof'): stats.add(os.path.join('prof', fn)) stats.dump_stats(os.path.join('prof', 'combined.pstats')) + + +def _get_version_tag(tag): + """Handle tags like pyqt>=5.3.1 for BDD tests. + + This transforms e.g. pyqt>=5.3.1 into an appropriate @pytest.mark.skip + marker, and falls back to pytest-bdd's implementation for all other + casesinto an appropriate @pytest.mark.skip marker, and falls back to + """ + version_re = re.compile(r""" + (?Pqt|pyqt) + (?P==|>|>=|<|<=|!=) + (?P\d+\.\d+\.\d+) + """, re.VERBOSE) + + match = version_re.match(tag) + if not match: + return None + + operators = { + '==': operator.eq, + '>': operator.gt, + '<': operator.lt, + '>=': operator.ge, + '<=': operator.le, + '!=': operator.ne, + } + + package = match.group('package') + op = operators[match.group('operator')] + version = match.group('version') + + if package == 'qt': + return pytest.mark.skipif(qtutils.version_check(version, op), + reason='Needs ' + tag) + elif package == 'pyqt': + major, minor, patch = [int(e) for e in version.split('.')] + hex_version = (major << 16) | (minor << 8) | patch + return pytest.mark.skipif(not op(PYQT_VERSION, hex_version), + reason='Needs ' + tag) + else: + raise ValueError("Invalid package {!r}".format(package)) + + +def _get_backend_tag(tag): + """Handle a @qtwebengine_*/@qtwebkit_skip tag.""" + pytest_marks = { + 'qtwebengine_todo': pytest.mark.qtwebengine_todo, + 'qtwebengine_skip': pytest.mark.qtwebengine_skip, + 'qtwebkit_skip': pytest.mark.qtwebkit_skip + } + if not any(tag.startswith(t + ':') for t in pytest_marks): + return None + name, desc = tag.split(':', maxsplit=1) + return pytest_marks[name](desc) + + +if not getattr(sys, 'frozen', False): + def pytest_bdd_apply_tag(tag, function): + """Handle custom tags for BDD tests. + + This tries various functions, and if none knows how to handle this tag, + it returns None so it falls back to pytest-bdd's implementation. + """ + funcs = [_get_version_tag, _get_backend_tag] + for func in funcs: + mark = func(tag) + if mark is not None: + mark(function) + return True + return None + + +def pytest_collection_modifyitems(config, items): + """Apply @qtwebengine_* markers; skip unittests with QUTE_BDD_WEBENGINE.""" + vercheck = qtutils.version_check + qtbug_54419_fixed = ((vercheck('5.6.2') and not vercheck('5.7.0')) or + qtutils.version_check('5.7.1') or + os.environ.get('QUTE_QTBUG54419_PATCHED', '')) + + markers = [ + ('qtwebengine_createWindow', 'Skipped because of QTBUG-54419', + pytest.mark.skipif, not qtbug_54419_fixed and config.webengine), + ('qtwebengine_todo', 'QtWebEngine TODO', pytest.mark.xfail, + config.webengine), + ('qtwebengine_skip', 'Skipped with QtWebEngine', pytest.mark.skipif, + config.webengine), + ('qtwebkit_skip', 'Skipped with QtWebKit', pytest.mark.skipif, + not config.webengine), + ] + + for item in items: + for name, prefix, pytest_mark, condition in markers: + marker = item.get_marker(name) + if marker and condition: + if marker.args: + text = '{}: {}'.format(prefix, marker.args[0]) + else: + text = prefix + item.add_marker(pytest_mark(condition, reason=text, + **marker.kwargs)) diff --git a/tests/end2end/data/javascript/issue906.html b/tests/end2end/data/javascript/issue906.html deleted file mode 100644 index dbbf54ee9..000000000 --- a/tests/end2end/data/javascript/issue906.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - diff --git a/tests/end2end/data/javascript/window_open.html b/tests/end2end/data/javascript/window_open.html new file mode 100644 index 000000000..b8e178db3 --- /dev/null +++ b/tests/end2end/data/javascript/window_open.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + diff --git a/tests/end2end/features/backforward.feature b/tests/end2end/features/backforward.feature index 1b43cac4d..835ad9351 100644 --- a/tests/end2end/features/backforward.feature +++ b/tests/end2end/features/backforward.feature @@ -88,7 +88,6 @@ Feature: Going back and forward. - url: http://localhost:*/data/backforward/2.txt - url: http://localhost:*/data/backforward/3.txt - @qtwebengine_skip: Causes 'Ignoring invalid URL being added to history' sometimes? Scenario: Going back too much with count. Given I open data/backforward/1.txt When I open data/backforward/2.txt @@ -138,7 +137,6 @@ Feature: Going back and forward. When I run :forward Then the error "At end of history." should be shown - @qtwebengine_skip: Causes 'Ignoring invalid URL being added to history' sometimes? Scenario: Going forward too much with count. Given I open data/backforward/1.txt When I open data/backforward/2.txt diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py index 5738531fa..e911b14a5 100644 --- a/tests/end2end/features/conftest.py +++ b/tests/end2end/features/conftest.py @@ -34,27 +34,6 @@ from qutebrowser.utils import log from helpers import utils -def pytest_collection_modifyitems(config, items): - """Apply @qtwebengine_* markers.""" - webengine = config.getoption('--qute-bdd-webengine') - - markers = { - 'qtwebengine_todo': ('QtWebEngine TODO', pytest.mark.xfail), - 'qtwebengine_skip': ('Skipped with QtWebEngine', pytest.mark.skipif), - } - - for item in items: - for name, (prefix, pytest_mark) in markers.items(): - marker = item.get_marker(name) - if marker: - if marker.args: - text = '{}: {}'.format(prefix, marker.args[0]) - else: - text = prefix - item.add_marker(pytest_mark(webengine, reason=text, - **marker.kwargs)) - - @pytest.hookimpl(hookwrapper=True) def pytest_runtest_makereport(item, call): """Add a BDD section to the test output.""" @@ -429,7 +408,7 @@ def compare_session(request, quteproc, expected): partial_compare is used, which means only the keys/values listed will be compared. """ - if request.config.getoption('--qute-bdd-webengine'): + if request.config.webengine: pytest.xfail(reason="QtWebEngine TODO: Sessions are not implemented") quteproc.compare_session(expected) @@ -494,7 +473,7 @@ def check_open_tabs(quteproc, request, tabs): It expects a list of URLs, with an optional "(active)" suffix. """ - if request.config.getoption('--qute-bdd-webengine'): + if request.config.webengine: pytest.xfail(reason="QtWebEngine TODO: Sessions are not implemented") session = quteproc.get_session() active_suffix = ' (active)' @@ -551,7 +530,7 @@ def _get_scroll_values(quteproc): @bdd.then(bdd.parsers.re(r"the page should be scrolled " r"(?Phorizontally|vertically)")) def check_scrolled(request, quteproc, direction): - if request.config.getoption('--qute-bdd-webengine'): + if request.config.webengine: pytest.xfail(reason="QtWebEngine TODO: Sessions are not implemented") x, y = _get_scroll_values(quteproc) if direction == 'horizontally': @@ -564,7 +543,7 @@ def check_scrolled(request, quteproc, direction): @bdd.then("the page should not be scrolled") def check_not_scrolled(request, quteproc): - if request.config.getoption('--qute-bdd-webengine'): + if request.config.webengine: pytest.xfail(reason="QtWebEngine TODO: Sessions are not implemented") x, y = _get_scroll_values(quteproc) assert x == 0 diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index 683defd8c..da0c1b603 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -9,6 +9,7 @@ Feature: Using hints And I hint with args "links normal" and follow xyz Then the error "No hint xyz!" should be shown + @qtwebengine_skip: Flaky because scrolling happens async Scenario: Following a link after scrolling down When I open data/scroll/simple.html And I run :hint links normal @@ -19,7 +20,7 @@ Feature: Using hints ### Opening in current or new tab - @qtwebengine_todo: createWindow is not implemented yet + @qtwebengine_createWindow Scenario: Following a hint and force to open in current tab. When I open data/hints/link_blank.html And I hint with args "links current" and follow a @@ -27,7 +28,7 @@ Feature: Using hints Then the following tabs should be open: - data/hello.txt (active) - @qtwebengine_todo: createWindow is not implemented yet + @qtwebengine_createWindow Scenario: Following a hint and allow to open in new tab. When I open data/hints/link_blank.html And I hint with args "links normal" and follow a @@ -36,7 +37,7 @@ Feature: Using hints - data/hints/link_blank.html - data/hello.txt (active) - @qtwebengine_todo: createWindow is not implemented yet + @qtwebengine_createWindow Scenario: Following a hint to link with sub-element and force to open in current tab. When I open data/hints/link_span.html And I run :tab-close @@ -154,13 +155,13 @@ Feature: Using hints And I hint wht args "links normal" and follow a Then "navigation request: url http://localhost:*/data/hello2.txt, type NavigationTypeLinkClicked, *" should be logged - @qtwebengine_todo: createWindow is not implemented yet + @qtwebengine_createWindow Scenario: Opening a link inside a specific iframe When I open data/hints/iframe_target.html And I hint with args "links normal" and follow a Then "navigation request: url http://localhost:*/data/hello.txt, type NavigationTypeLinkClicked, *" should be logged - @qtwebengine_todo: createWindow is not implemented yet + @qtwebengine_createWindow Scenario: Opening a link with specific target frame in a new tab When I open data/hints/iframe_target.html And I hint with args "links tab" and follow a @@ -172,7 +173,7 @@ Feature: Using hints ### hints -> auto-follow-timeout Scenario: Ignoring key presses after auto-following hints - When I set hints -> auto-follow-timeout to 200 + When I set hints -> auto-follow-timeout to 500 And I set hints -> mode to number And I run :bind --force , message-error "This should not happen" And I open data/hints/html/simple.html diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index badb26aac..165bdd792 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -48,6 +48,12 @@ Feature: Page history Then the history file should contain: http://localhost:(port)/status/404 Error loading page: http://localhost:(port)/status/404 + @qtwebengine_createWindow + Scenario: History with invalid URL + When I open data/javascript/window_open.html + And I run :click-element id open-invalid + Then "Changing title for idx 1 to 'about:blank'" should be logged + Scenario: Clearing history When I open data/title.html And I run :history-clear diff --git a/tests/end2end/features/javascript.feature b/tests/end2end/features/javascript.feature index b95e4e7d7..a86d88e20 100644 --- a/tests/end2end/features/javascript.feature +++ b/tests/end2end/features/javascript.feature @@ -7,14 +7,67 @@ Feature: Javascript stuff And I open data/javascript/consolelog.html Then the javascript message "console.log works!" should be logged + # Causes segfaults... + @qtwebengine_createWindow + Scenario: Opening/Closing a window via JS + When I open data/javascript/window_open.html + And I run :tab-only + And I run :click-element id open-normal + And I wait for "Changing title for idx 1 to 'about:blank'" in the log + And I run :tab-focus 1 + And I run :click-element id close-normal + Then "Focus object changed: *" should be logged + + # Causes segfaults... + @qtwebengine_createWindow + Scenario: Opening/closing a modal window via JS + When I open data/javascript/window_open.html + And I run :tab-only + And I run :click-element id open-modal + And I wait for "Changing title for idx 1 to 'about:blank'" in the log + And I run :tab-focus 1 + And I run :click-element id close-normal + Then "Focus object changed: *" should be logged + # WebModalDialog with QtWebKit, WebDialog with QtWebEngine + And "Web*Dialog requested, but we don't support that!" should be logged + # https://github.com/The-Compiler/qutebrowser/issues/906 - @qtwebengine_todo: createWindow is not implemented yet - Scenario: Closing a JS window twice (issue 906) + @qtwebengine_skip + Scenario: Closing a JS window twice (issue 906) - qtwebkit When I open about:blank - And I open data/javascript/issue906.html in a new tab - And I run :click-element id open-button + And I run :tab-only + When I open data/javascript/window_open.html in a new tab + And I run :click-element id open-normal And I wait for "Changing title for idx 2 to 'about:blank'" in the log And I run :tab-focus 2 - And I run :click-element id close-button + And I run :click-element id close-twice Then "Requested to close * which does not exist!" should be logged + + @qtwebengine_createWindow @qtwebkit_skip + Scenario: Closing a JS window twice (issue 906) - qtwebengine + When I open about:blank + And I run :tab-only + And I open data/javascript/window_open.html in a new tab + And I run :click-element id open-normal + And I wait for "Changing title for idx 2 to 'about:blank'" in the log + And I run :tab-focus 2 + And I run :click-element id close-twice + And I wait for "Focus object changed: *" in the log + Then no crash should happen + + @qtwebengine_createWindow + Scenario: Opening window without user interaction with javascript-can-open-windows-automatically set to true + When I open data/hello.txt + And I set content -> javascript-can-open-windows-automatically to true + And I run :tab-only + And I run :jseval if (window.open('about:blank')) { console.log('window opened'); } else { console.log('error while opening window'); } + Then the javascript message "window opened" should be logged + + @qtwebengine_createWindow + Scenario: Opening window without user interaction with javascript-can-open-windows-automatically set to false + When I open data/hello.txt + And I set content -> javascript-can-open-windows-automatically to false + And I run :tab-only + And I run :jseval if (window.open('about:blank')) { console.log('window opened'); } else { console.log('error while opening window'); } + Then the javascript message "error while opening window" should be logged diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 493e6e8af..1c5a25bf4 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -551,7 +551,7 @@ Feature: Various utility commands. Then the page should not be scrolled And the error "prompt-accept: This command is only allowed in prompt/yesno mode." should be shown - @qtwebengine_todo: createWindow is not implemented yet + @qtwebengine_createWindow Scenario: :repeat-command with mode-switching command Given I open data/hints/link_blank.html And I run :tab-only @@ -620,6 +620,7 @@ Feature: Various utility commands. - data/click_element.html - data/hello.txt (active) + @qtwebengine_skip: Flaky because scrolling happens async Scenario: Clicking an element which is out of view When I open data/scroll/simple.html And I run :scroll-page 0 1 diff --git a/tests/end2end/features/test_marks_bdd.py b/tests/end2end/features/test_marks_bdd.py index 858456996..58aec5a00 100644 --- a/tests/end2end/features/test_marks_bdd.py +++ b/tests/end2end/features/test_marks_bdd.py @@ -24,7 +24,7 @@ bdd.scenarios('marks.feature') @bdd.then(bdd.parsers.parse("the page should be scrolled to {x} {y}")) def check_y(request, quteproc, x, y): - if request.config.getoption('--qute-bdd-webengine'): + if request.config.webengine: pytest.xfail(reason="QtWebEngine TODO: Sessions are not implemented") data = quteproc.get_session() pos = data['windows'][0]['tabs'][0]['history'][-1]['scroll-pos'] diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 5b621af47..778ae732f 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -157,7 +157,6 @@ class QuteProc(testprocess.Process): def __init__(self, request, *, parent=None): super().__init__(parent) - self._webengine = request.config.getoption('--qute-bdd-webengine') self._ipc_socket = None self.basedir = None self._focus_ready = False @@ -261,7 +260,7 @@ class QuteProc(testprocess.Process): return executable, args def _default_args(self): - backend = 'webengine' if self._webengine else 'webkit' + backend = 'webengine' if self.request.config.webengine else 'webkit' return ['--debug', '--no-err-windows', '--temp-basedir', '--json-logging', '--backend', backend, 'about:blank'] @@ -338,7 +337,7 @@ class QuteProc(testprocess.Process): ('general', 'auto-save-interval', '0'), ('general', 'new-instance-open-target.window', 'last-opened') ] - if not self._webengine: + if not self.request.config.webengine: settings.append(('network', 'ssl-strict', 'false')) for sect, opt, value in settings: diff --git a/tests/end2end/fixtures/testprocess.py b/tests/end2end/fixtures/testprocess.py index 86a883aea..c8868e2a5 100644 --- a/tests/end2end/fixtures/testprocess.py +++ b/tests/end2end/fixtures/testprocess.py @@ -234,7 +234,7 @@ class Process(QObject): if not self.is_running(): # _start ensures it actually started, but it might quit shortly # afterwards - raise ProcessExited() + raise ProcessExited('\n' + _render_log(self.captured_log)) if blocker.signal_triggered: self._after_start() diff --git a/tests/end2end/test_hints_html.py b/tests/end2end/test_hints_html.py index 406e1bd46..c2d5143bc 100644 --- a/tests/end2end/test_hints_html.py +++ b/tests/end2end/test_hints_html.py @@ -85,21 +85,20 @@ def _parse_file(test_name): @pytest.mark.parametrize('find_implementation', ['javascript', 'python']) def test_hints(test_name, zoom_text_only, zoom_level, find_implementation, quteproc, request): - webengine = bool(request.config.getoption('--qute-bdd-webengine')) - if zoom_text_only and webengine: + if zoom_text_only and request.config.webengine: pytest.skip("QtWebEngine doesn't have zoom-text-only") - if find_implementation == 'python' and webengine: + if find_implementation == 'python' and request.config.webengine: pytest.skip("QtWebEngine doesn't have a python find implementation") parsed = _parse_file(test_name) - if parsed.qtwebengine_todo is not None and webengine: + if parsed.qtwebengine_todo is not None and request.config.webengine: pytest.xfail("QtWebEngine TODO: {}".format(parsed.qtwebengine_todo)) url_path = 'data/hints/html/{}'.format(test_name) quteproc.open_path(url_path) # setup - if not webengine: + if not request.config.webengine: quteproc.set_setting('ui', 'zoom-text-only', str(zoom_text_only)) quteproc.set_setting('hints', 'find-implementation', find_implementation) @@ -111,7 +110,7 @@ def test_hints(test_name, zoom_text_only, zoom_level, find_implementation, quteproc.wait_for_load_finished('data/' + parsed.target) # reset quteproc.send_cmd(':zoom 100') - if not webengine: + if not request.config.webengine: quteproc.set_setting('ui', 'zoom-text-only', 'false') quteproc.set_setting('hints', 'find-implementation', 'javascript') diff --git a/tests/end2end/test_insert_mode.py b/tests/end2end/test_insert_mode.py index f8dc7a547..a3edd651e 100644 --- a/tests/end2end/test_insert_mode.py +++ b/tests/end2end/test_insert_mode.py @@ -48,7 +48,7 @@ def test_insert_mode(file_name, elem_id, source, input_text, auto_insert, if source == 'keypress': quteproc.press_keys(input_text) elif source == 'clipboard': - if request.config.getoption('--qute-bdd-webengine'): + if request.config.webengine: pytest.xfail(reason="QtWebEngine TODO: :insert-text is not " "implemented") quteproc.send_cmd(':debug-set-fake-clipboard "{}"'.format(input_text)) diff --git a/tests/unit/misc/test_crashdialog.py b/tests/unit/misc/test_crashdialog.py index 6f38cd50c..dd2ebd35e 100644 --- a/tests/unit/misc/test_crashdialog.py +++ b/tests/unit/misc/test_crashdialog.py @@ -76,6 +76,7 @@ def test_parse_fatal_stacktrace(text, typ, func): "QT_IM_MODULE = fcitx" ), ({'LANGUAGE': 'foo', 'LANG': 'en_US.UTF-8'}, "LANG = en_US.UTF-8"), + ({'FOO': 'bar', 'QUTE_BLAH': '1'}, "QUTE_BLAH = 1"), ], ids=lambda e: e[1]) def test_get_environment_vars(monkeypatch, env, expected): """Test for crashdialog._get_environment_vars.""" diff --git a/tox.ini b/tox.ini index f6a6b88cd..5c49ecce4 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ skipsdist = true setenv = QT_QPA_PLATFORM_PLUGIN_PATH={envdir}/Lib/site-packages/PyQt5/plugins/platforms PYTEST_QT_API=pyqt5 -passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI TRAVIS XDG_* +passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI TRAVIS XDG_* QUTE_* deps = -r{toxinidir}/misc/requirements/requirements-pip.txt -r{toxinidir}/requirements.txt