summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <me@the-compiler.org>2018-09-26 11:20:45 +0200
committerFlorian Bruhin <me@the-compiler.org>2018-09-26 11:20:45 +0200
commita292664ca0f42f6c4c4bc7853f5fedcb35757428 (patch)
tree997df4bfc092b6ca895f8f15384f7f9b4da492f8
parent133ed5604e3180a6feebe1c358e9eab3652f6c0c (diff)
parent119a60d498a9e92c3936b7834712e4d19a5353d8 (diff)
downloadqutebrowser-a292664ca0f42f6c4c4bc7853f5fedcb35757428.tar.gz
qutebrowser-a292664ca0f42f6c4c4bc7853f5fedcb35757428.zip
Merge remote-tracking branch 'origin/pr/4218'
-rw-r--r--qutebrowser/browser/browsertab.py2
-rw-r--r--qutebrowser/browser/webengine/webenginetab.py73
-rw-r--r--qutebrowser/browser/webkit/webkittab.py8
-rw-r--r--qutebrowser/javascript/caret.js167
-rw-r--r--qutebrowser/utils/javascript.py2
-rw-r--r--scripts/dev/ci/travis_run.sh4
-rw-r--r--scripts/dev/run_pylint_on_tests.py10
-rw-r--r--tests/conftest.py1
-rw-r--r--tests/end2end/features/caret.feature277
-rw-r--r--tests/helpers/fixtures.py83
-rw-r--r--tests/helpers/stubs.py3
-rw-r--r--tests/helpers/utils.py1
-rw-r--r--tests/unit/browser/test_caret.py359
-rw-r--r--tests/unit/utils/test_javascript.py1
-rw-r--r--tox.ini2
15 files changed, 582 insertions, 411 deletions
diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py
index 087834e53..0eb9b6493 100644
--- a/qutebrowser/browser/browsertab.py
+++ b/qutebrowser/browser/browsertab.py
@@ -395,9 +395,11 @@ class AbstractCaret(QObject):
Signals:
selection_toggled: Emitted when the selection was toggled.
arg: Whether the selection is now active.
+ follow_selected_done: Emitted when a follow_selection action is done.
"""
selection_toggled = pyqtSignal(bool)
+ follow_selected_done = pyqtSignal()
def __init__(self, tab, mode_manager, parent=None):
super().__init__(parent)
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
index cfb809097..36ac2a99a 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -21,12 +21,11 @@
import math
import functools
-import sys
import re
import html as html_utils
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF,
- QUrl, QTimer, QObject, qVersion)
+ QUrl, QTimer, QObject)
from PyQt5.QtGui import QKeyEvent, QIcon
from PyQt5.QtNetwork import QAuthenticator
from PyQt5.QtWidgets import QApplication
@@ -234,6 +233,15 @@ class WebEngineCaret(browsertab.AbstractCaret):
"""QtWebEngine implementations related to moving the cursor/selection."""
+ def _flags(self):
+ """Get flags to pass to JS."""
+ flags = set()
+ if qtutils.version_check('5.7.1', compiled=False):
+ flags.add('filter-prefix')
+ if utils.is_windows:
+ flags.add('windows')
+ return list(flags)
+
@pyqtSlot(usertypes.KeyMode)
def _on_mode_entered(self, mode):
if mode != usertypes.KeyMode.caret:
@@ -246,9 +254,9 @@ class WebEngineCaret(browsertab.AbstractCaret):
self._tab.search.clear()
self._tab.run_js_async(
- javascript.assemble('caret',
- 'setPlatform', sys.platform, qVersion()))
- self._js_call('setInitialCursor', self._selection_cb)
+ javascript.assemble('caret', 'setFlags', self._flags()))
+
+ self._js_call('setInitialCursor', callback=self._selection_cb)
def _selection_cb(self, enabled):
"""Emit selection_toggled based on setInitialCursor."""
@@ -266,32 +274,25 @@ class WebEngineCaret(browsertab.AbstractCaret):
self._js_call('disableCaret')
def move_to_next_line(self, count=1):
- for _ in range(count):
- self._js_call('moveDown')
+ self._js_call('moveDown', count)
def move_to_prev_line(self, count=1):
- for _ in range(count):
- self._js_call('moveUp')
+ self._js_call('moveUp', count)
def move_to_next_char(self, count=1):
- for _ in range(count):
- self._js_call('moveRight')
+ self._js_call('moveRight', count)
def move_to_prev_char(self, count=1):
- for _ in range(count):
- self._js_call('moveLeft')
+ self._js_call('moveLeft', count)
def move_to_end_of_word(self, count=1):
- for _ in range(count):
- self._js_call('moveToEndOfWord')
+ self._js_call('moveToEndOfWord', count)
def move_to_next_word(self, count=1):
- for _ in range(count):
- self._js_call('moveToNextWord')
+ self._js_call('moveToNextWord', count)
def move_to_prev_word(self, count=1):
- for _ in range(count):
- self._js_call('moveToPreviousWord')
+ self._js_call('moveToPreviousWord', count)
def move_to_start_of_line(self):
self._js_call('moveToStartOfLine')
@@ -300,20 +301,16 @@ class WebEngineCaret(browsertab.AbstractCaret):
self._js_call('moveToEndOfLine')
def move_to_start_of_next_block(self, count=1):
- for _ in range(count):
- self._js_call('moveToStartOfNextBlock')
+ self._js_call('moveToStartOfNextBlock', count)
def move_to_start_of_prev_block(self, count=1):
- for _ in range(count):
- self._js_call('moveToStartOfPrevBlock')
+ self._js_call('moveToStartOfPrevBlock', count)
def move_to_end_of_next_block(self, count=1):
- for _ in range(count):
- self._js_call('moveToEndOfNextBlock')
+ self._js_call('moveToEndOfNextBlock', count)
def move_to_end_of_prev_block(self, count=1):
- for _ in range(count):
- self._js_call('moveToEndOfPrevBlock')
+ self._js_call('moveToEndOfPrevBlock', count)
def move_to_start_of_document(self):
self._js_call('moveToStartOfDocument')
@@ -322,7 +319,7 @@ class WebEngineCaret(browsertab.AbstractCaret):
self._js_call('moveToEndOfDocument')
def toggle_selection(self):
- self._js_call('toggleSelection', self.selection_toggled.emit)
+ self._js_call('toggleSelection', callback=self.selection_toggled.emit)
def drop_selection(self):
self._js_call('dropSelection')
@@ -335,7 +332,13 @@ class WebEngineCaret(browsertab.AbstractCaret):
self._tab.run_js_async(javascript.assemble('caret', 'getSelection'),
callback)
- def _follow_selected_cb(self, js_elem, tab=False):
+ def _follow_selected_cb_wrapped(self, js_elem, tab):
+ try:
+ self._follow_selected_cb(js_elem, tab)
+ finally:
+ self.follow_selected_done.emit()
+
+ def _follow_selected_cb(self, js_elem, tab):
"""Callback for javascript which clicks the selected element.
Args:
@@ -344,6 +347,7 @@ class WebEngineCaret(browsertab.AbstractCaret):
"""
if js_elem is None:
return
+
if js_elem == "focused":
# we had a focused element, not a selected one. Just send <enter>
self._follow_enter(tab)
@@ -364,7 +368,6 @@ class WebEngineCaret(browsertab.AbstractCaret):
elem.click(click_type)
except webelem.Error as e:
message.error(str(e))
- return
def follow_selected(self, *, tab=False):
if self._tab.search.search_displayed:
@@ -380,11 +383,13 @@ class WebEngineCaret(browsertab.AbstractCaret):
# click an existing blue selection
js_code = javascript.assemble('webelem',
'find_selected_focused_link')
- self._tab.run_js_async(js_code, lambda jsret:
- self._follow_selected_cb(jsret, tab))
+ self._tab.run_js_async(
+ js_code,
+ lambda jsret: self._follow_selected_cb_wrapped(jsret, tab))
- def _js_call(self, command, callback=None):
- self._tab.run_js_async(javascript.assemble('caret', command), callback)
+ def _js_call(self, command, *args, callback=None):
+ code = javascript.assemble('caret', command, *args)
+ self._tab.run_js_async(code, callback)
class WebEngineScroller(browsertab.AbstractScroller):
diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py
index 7b7ad0c7d..7f0740b65 100644
--- a/qutebrowser/browser/webkit/webkittab.py
+++ b/qutebrowser/browser/webkit/webkittab.py
@@ -348,7 +348,7 @@ class WebKitCaret(browsertab.AbstractCaret):
def selection(self, callback):
callback(self._widget.selectedText())
- def follow_selected(self, *, tab=False):
+ def _follow_selected(self, *, tab=False):
if QWebSettings.globalSettings().testAttribute(
QWebSettings.JavascriptEnabled):
if tab:
@@ -389,6 +389,12 @@ class WebKitCaret(browsertab.AbstractCaret):
else:
self._tab.openurl(url)
+ def follow_selected(self, *, tab=False):
+ try:
+ self._follow_selected(tab=tab)
+ finally:
+ self.follow_selected_done.emit()
+
class WebKitZoom(browsertab.AbstractZoom):
diff --git a/qutebrowser/javascript/caret.js b/qutebrowser/javascript/caret.js
index 3f858f3fb..8f3ec8d72 100644
--- a/qutebrowser/javascript/caret.js
+++ b/qutebrowser/javascript/caret.js
@@ -756,31 +756,6 @@ window._qutebrowser.caret = (function() {
CaretBrowsing.isSelectionCollapsed = false;
/**
- * The id returned by window.setInterval for our blink function, so
- * we can cancel it when caret browsing is disabled.
- * @type {number?}
- */
- CaretBrowsing.blinkFunctionId = null;
-
- /**
- * The desired x-coordinate to match when moving the caret up and down.
- * To match the behavior as documented in Mozilla's caret browsing spec
- * (http://www.mozilla.org/access/keyboard/proposal), we keep track of the
- * initial x position when the user starts moving the caret up and down,
- * so that the x position doesn't drift as you move throughout lines, but
- * stays as close as possible to the initial position. This is reset when
- * moving left or right or clicking.
- * @type {number?}
- */
- CaretBrowsing.targetX = null;
-
- /**
- * A flag that flips on or off as the caret blinks.
- * @type {boolean}
- */
- CaretBrowsing.blinkFlag = true;
-
- /**
* Whether we're running on Windows.
* @type {boolean}
*/
@@ -788,9 +763,17 @@ window._qutebrowser.caret = (function() {
/**
* Whether we're running on on old Qt 5.7.1.
+ * There, we need to use -webkit-filter.
* @type {boolean}
*/
- CaretBrowsing.isOldQt = null;
+ CaretBrowsing.needsFilterPrefix = null;
+
+ /**
+ * The id returned by window.setInterval for our stopAnimation function, so
+ * we can cancel it when we call stopAnimation again.
+ * @type {number?}
+ */
+ CaretBrowsing.animationFunctionId = null;
/**
* Check if a node is a control that normally allows the user to interact
@@ -868,7 +851,7 @@ window._qutebrowser.caret = (function() {
};
CaretBrowsing.injectCaretStyles = function() {
- const prefix = CaretBrowsing.isOldQt ? "-webkit-" : "";
+ const prefix = CaretBrowsing.needsFilterPrefix ? "-webkit-" : "";
const style = `
.CaretBrowsing_Caret {
position: absolute;
@@ -987,7 +970,6 @@ window._qutebrowser.caret = (function() {
*/
CaretBrowsing.recreateCaretElement = function() {
if (CaretBrowsing.caretElement) {
- window.clearInterval(CaretBrowsing.blinkFunctionId);
CaretBrowsing.caretElement.parentElement.removeChild(
CaretBrowsing.caretElement);
CaretBrowsing.caretElement = null;
@@ -1163,47 +1145,47 @@ window._qutebrowser.caret = (function() {
}
};
- CaretBrowsing.move = function(direction, granularity) {
+ CaretBrowsing.move = function(direction, granularity, count = 1) {
let action = "move";
if (CaretBrowsing.selectionEnabled) {
action = "extend";
}
- window.
- getSelection().
- modify(action, direction, granularity);
+
+ for (let i = 0; i < count; i++) {
+ window.
+ getSelection().
+ modify(action, direction, granularity);
+ }
if (CaretBrowsing.isWindows &&
(direction === "forward" ||
direction === "right") &&
granularity === "word") {
CaretBrowsing.move("left", "character");
- } else {
- window.setTimeout(() => {
- CaretBrowsing.updateCaretOrSelection(true);
- }, 0);
}
+ };
+ CaretBrowsing.finishMove = function() {
+ window.setTimeout(() => {
+ CaretBrowsing.updateCaretOrSelection(true);
+ }, 0);
CaretBrowsing.stopAnimation();
};
- CaretBrowsing.moveToBlock = function(paragraph, boundary) {
+ CaretBrowsing.moveToBlock = function(paragraph, boundary, count = 1) {
let action = "move";
if (CaretBrowsing.selectionEnabled) {
action = "extend";
}
- window.
- getSelection().
- modify(action, paragraph, "paragraph");
-
- window.
- getSelection().
- modify(action, boundary, "paragraphboundary");
+ for (let i = 0; i < count; i++) {
+ window.
+ getSelection().
+ modify(action, paragraph, "paragraph");
- window.setTimeout(() => {
- CaretBrowsing.updateCaretOrSelection(true);
- }, 0);
-
- CaretBrowsing.stopAnimation();
+ window.
+ getSelection().
+ modify(action, boundary, "paragraphboundary");
+ }
};
CaretBrowsing.toggle = function(value) {
@@ -1232,7 +1214,6 @@ window._qutebrowser.caret = (function() {
return true;
}
window.setTimeout(() => {
- CaretBrowsing.targetX = null;
CaretBrowsing.updateCaretOrSelection(false);
}, 0);
return true;
@@ -1250,7 +1231,6 @@ window._qutebrowser.caret = (function() {
CaretBrowsing.updateCaretOrSelection(true);
} else if (!CaretBrowsing.isCaretVisible &&
CaretBrowsing.caretElement) {
- window.clearInterval(CaretBrowsing.blinkFunctionId);
if (CaretBrowsing.caretElement) {
CaretBrowsing.isSelectionCollapsed = false;
CaretBrowsing.caretElement.parentElement.removeChild(
@@ -1271,14 +1251,20 @@ window._qutebrowser.caret = (function() {
};
CaretBrowsing.startAnimation = function() {
- CaretBrowsing.caretElement.style.animationIterationCount = "infinite";
+ if (CaretBrowsing.caretElement) {
+ CaretBrowsing.caretElement.style.animationIterationCount = "infinite";
+ }
};
CaretBrowsing.stopAnimation = function() {
- CaretBrowsing.caretElement.style.animationIterationCount = 0;
- window.setTimeout(() => {
- CaretBrowsing.startAnimation();
- }, 1000);
+ if (CaretBrowsing.caretElement) {
+ CaretBrowsing.caretElement.style.animationIterationCount = 0;
+ window.clearTimeout(CaretBrowsing.animationFunctionId);
+
+ CaretBrowsing.animationFunctionId = window.setTimeout(() => {
+ CaretBrowsing.startAnimation();
+ }, 1000);
+ }
};
CaretBrowsing.init = function() {
@@ -1318,9 +1304,9 @@ window._qutebrowser.caret = (function() {
return CaretBrowsing.selectionEnabled;
};
- funcs.setPlatform = (platform, qtVersion) => {
- CaretBrowsing.isWindows = platform.startsWith("win");
- CaretBrowsing.isOldQt = qtVersion === "5.7.1";
+ funcs.setFlags = (flags) => {
+ CaretBrowsing.isWindows = flags.includes("windows");
+ CaretBrowsing.needsFilterPrefix = flags.includes("filter-prefix");
};
funcs.disableCaret = () => {
@@ -1331,67 +1317,80 @@ window._qutebrowser.caret = (function() {
CaretBrowsing.toggle();
};
- funcs.moveRight = () => {
- CaretBrowsing.move("right", "character");
+ funcs.moveRight = (count = 1) => {
+ CaretBrowsing.move("right", "character", count);
+ CaretBrowsing.finishMove();
};
- funcs.moveLeft = () => {
- CaretBrowsing.move("left", "character");
+ funcs.moveLeft = (count = 1) => {
+ CaretBrowsing.move("left", "character", count);
+ CaretBrowsing.finishMove();
};
- funcs.moveDown = () => {
- CaretBrowsing.move("forward", "line");
+ funcs.moveDown = (count = 1) => {
+ CaretBrowsing.move("forward", "line", count);
+ CaretBrowsing.finishMove();
};
- funcs.moveUp = () => {
- CaretBrowsing.move("backward", "line");
+ funcs.moveUp = (count = 1) => {
+ CaretBrowsing.move("backward", "line", count);
+ CaretBrowsing.finishMove();
};
- funcs.moveToEndOfWord = () => {
- funcs.moveToNextWord();
- funcs.moveLeft();
+ funcs.moveToEndOfWord = (count = 1) => {
+ CaretBrowsing.move("forward", "word", count);
+ CaretBrowsing.finishMove();
};
- funcs.moveToNextWord = () => {
- CaretBrowsing.move("forward", "word");
- funcs.moveRight();
+ funcs.moveToNextWord = (count = 1) => {
+ CaretBrowsing.move("forward", "word", count);
+ CaretBrowsing.move("right", "character");
+ CaretBrowsing.finishMove();
};
- funcs.moveToPreviousWord = () => {
- CaretBrowsing.move("backward", "word");
+ funcs.moveToPreviousWord = (count = 1) => {
+ CaretBrowsing.move("backward", "word", count);
+ CaretBrowsing.finishMove();
};
funcs.moveToStartOfLine = () => {
CaretBrowsing.move("left", "lineboundary");
+ CaretBrowsing.finishMove();
};
funcs.moveToEndOfLine = () => {
CaretBrowsing.move("right", "lineboundary");
+ CaretBrowsing.finishMove();
};
- funcs.moveToStartOfNextBlock = () => {
- CaretBrowsing.moveToBlock("forward", "backward");
+ funcs.moveToStartOfNextBlock = (count = 1) => {
+ CaretBrowsing.moveToBlock("forward", "backward", count);
+ CaretBrowsing.finishMove();
};
- funcs.moveToStartOfPrevBlock = () => {
- CaretBrowsing.moveToBlock("backward", "backward");
+ funcs.moveToStartOfPrevBlock = (count = 1) => {
+ CaretBrowsing.moveToBlock("backward", "backward", count);
+ CaretBrowsing.finishMove();
};
- funcs.moveToEndOfNextBlock = () => {
- CaretBrowsing.moveToBlock("forward", "forward");
+ funcs.moveToEndOfNextBlock = (count = 1) => {
+ CaretBrowsing.moveToBlock("forward", "forward", count);
+ CaretBrowsing.finishMove();
};
- funcs.moveToEndOfPrevBlock = () => {
- CaretBrowsing.moveToBlock("backward", "forward");
+ funcs.moveToEndOfPrevBlock = (count = 1) => {
+ CaretBrowsing.moveToBlock("backward", "forward", count);
+ CaretBrowsing.finishMove();
};
funcs.moveToStartOfDocument = () => {
CaretBrowsing.move("backward", "documentboundary");
+ CaretBrowsing.finishMove();
};
funcs.moveToEndOfDocument = () => {
CaretBrowsing.move("forward", "documentboundary");
- funcs.moveLeft();
+ CaretBrowsing.finishMove();
};
funcs.dropSelection = () => {
diff --git a/qutebrowser/utils/javascript.py b/qutebrowser/utils/javascript.py
index 93df8e70f..21b373dd1 100644
--- a/qutebrowser/utils/javascript.py
+++ b/qutebrowser/utils/javascript.py
@@ -59,6 +59,8 @@ def _convert_js_arg(arg):
return str(arg).lower()
elif isinstance(arg, (int, float)):
return str(arg)
+ elif isinstance(arg, list):
+ return '[{}]'.format(', '.join(_convert_js_arg(e) for e in arg))
else:
raise TypeError("Don't know how to handle {!r} of type {}!".format(
arg, type(arg).__name__))
diff --git a/scripts/dev/ci/travis_run.sh b/scripts/dev/ci/travis_run.sh
index 55ca7c11e..a287e844e 100644
--- a/scripts/dev/ci/travis_run.sh
+++ b/scripts/dev/ci/travis_run.sh
@@ -28,5 +28,9 @@ else
args=()
[[ $TRAVIS_OS_NAME == osx ]] && args=('--qute-bdd-webengine' '--no-xvfb' 'tests/unit')
+ # WORKAROUND for unknown crash inside swrast_dri.so
+ # See https://github.com/qutebrowser/qutebrowser/pull/4218#issuecomment-421931770
+ [[ $TESTENV == py36-pyqt59 ]] && export QT_QUICK_BACKEND=software
+
tox -e "$TESTENV" -- "${args[@]}"
fi
diff --git a/scripts/dev/run_pylint_on_tests.py b/scripts/dev/run_pylint_on_tests.py
index 7adf45f76..d8523a4b4 100644
--- a/scripts/dev/run_pylint_on_tests.py
+++ b/scripts/dev/run_pylint_on_tests.py
@@ -52,6 +52,7 @@ def main():
# pytest fixtures
'redefined-outer-name',
'unused-argument',
+ 'too-many-arguments',
# things which are okay in tests
'missing-docstring',
'protected-access',
@@ -65,9 +66,12 @@ def main():
toxinidir,
]
- args = (['--disable={}'.format(','.join(disabled)),
- '--ignored-modules=helpers,pytest,PyQt5'] +
- sys.argv[2:] + files)
+ args = [
+ '--disable={}'.format(','.join(disabled)),
+ '--ignored-modules=helpers,pytest,PyQt5',
+ r'--ignore-long-lines=(<?https?://|^# Copyright 201\d)|^ *def [a-z]',
+ r'--method-rgx=[a-z_][A-Za-z0-9_]{1,100}$',
+ ] + sys.argv[2:] + files
env = os.environ.copy()
env['PYTHONPATH'] = os.pathsep.join(pythonpath)
diff --git a/tests/conftest.py b/tests/conftest.py
index 7168cd812..05564f370 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -43,6 +43,7 @@ import qutebrowser.app # To register commands
ON_CI = 'CI' in os.environ
+_qute_scheme_handler = None
# Set hypothesis settings
diff --git a/tests/end2end/features/caret.feature b/tests/end2end/features/caret.feature
index 6d2245a89..e540bafcb 100644
--- a/tests/end2end/features/caret.feature
+++ b/tests/end2end/features/caret.feature
@@ -7,224 +7,6 @@ Feature: Caret mode
Given I open data/caret.html
And I run :tab-only ;; enter-mode caret
- # document
-
- Scenario: Selecting the entire document
- When I run :toggle-selection
- And I run :move-to-end-of-document
- And I run :yank selection
- Then the clipboard should contain:
- one two three
- eins zwei drei
-
- four five six
- vier fünf sechs
-
- Scenario: Moving to end and to start of document
- When I run :move-to-end-of-document
- And I run :move-to-start-of-document
- And I run :toggle-selection
- And I run :move-to-end-of-word
- And I run :yank selection
- Then the clipboard should contain "one"
-
- Scenario: Moving to end and to start of document (with selection)
- When I run :move-to-end-of-document
- And I run :toggle-selection
- And I run :move-to-start-of-document
- And I run :yank selection
- Then the clipboard should contain:
- one two three
- eins zwei drei
-
- four five six
- vier fünf sechs
-
- # block
-
- Scenario: Selecting a block
- When I run :toggle-selection
- And I run :move-to-end-of-next-block
- And I run :yank selection
- Then the clipboard should contain:
- one two three
- eins zwei drei
-
- Scenario: Moving back to the end of previous block (with selection)
- When I run :move-to-end-of-next-block with count 2
- And I run :toggle-selection
- And I run :move-to-end-of-prev-block
- And I run :move-to-prev-word
- And I run :yank selection
- Then the clipboard should contain:
- drei
-
- four five six
-
- Scenario: Moving back to the end of previous block
- When I run :move-to-end-of-next-block with count 2
- And I run :move-to-end-of-prev-block
- And I run :toggle-selection
- And I run :move-to-prev-word
- And I run :yank selection
- Then the clipboard should contain "drei"
-
- Scenario: Moving back to the start of previous block (with selection)
- When I run :move-to-end-of-next-block with count 2
- And I run :toggle-selection
- And I run :move-to-start-of-prev-block
- And I run :yank selection
- Then the clipboard should contain:
- eins zwei drei
-
- four five six
-
- Scenario: Moving back to the start of previous block
- When I run :move-to-end-of-next-block with count 2
- And I run :move-to-start-of-prev-block
- And I run :toggle-selection
- And I run :move-to-next-word
- And I run :yank selection
- Then the clipboard should contain "eins "
-
- Scenario: Moving to the start of next block (with selection)
- When I run :toggle-selection
- And I run :move-to-start-of-next-block
- And I run :yank selection
- Then the clipboard should contain "one two three\n"
-
- Scenario: Moving to the start of next block
- When I run :move-to-start-of-next-block
- And I run :toggle-selection
- And I run :move-to-end-of-word
- And I run :yank selection
- Then the clipboard should contain "eins"
-
- # line
-
- Scenario: Selecting a line
- When I run :toggle-selection
- And I run :move-to-end-of-line
- And I run :yank selection
- Then the clipboard should contain "one two three"
-
- Scenario: Moving and selecting a line
- When I run :move-to-next-line
- And I run :toggle-selection
- And I run :move-to-end-of-line
- And I run :yank selection
- Then the clipboard should contain "eins zwei drei"
-
- Scenario: Selecting next line
- When I run :toggle-selection
- And I run :move-to-next-line
- And I run :yank selection
- Then the clipboard should contain "one two three\n"
-
- Scenario: Moving to end and to start of line
- When I run :move-to-end-of-line
- And I run :move-to-start-of-line
- And I run :toggle-selection
- And I run :move-to-end-of-word
- And I run :yank selection
- Then the clipboard should contain "one"
-
- Scenario: Selecting a line (backwards)
- When I run :move-to-end-of-line
- And I run :toggle-selection
- When I run :move-to-start-of-line
- And I run :yank selection
- Then the clipboard should contain "one two three"
-
- Scenario: Selecting previous line
- When I run :move-to-next-line
- And I run :toggle-selection
- When I run :move-to-prev-line
- And I run :yank selection
- Then the clipboard should contain "one two three\n"
-
- Scenario: Moving to previous line
- When I run :move-to-next-line
- When I run :move-to-prev-line
- And I run :toggle-selection
- When I run :move-to-next-line
- And I run :yank selection
- Then the clipboard should contain "one two three\n"
-
- # word
-
- Scenario: Selecting a word
- When I run :toggle-selection
- And I run :move-to-end-of-word
- And I run :yank selection
- Then the clipboard should contain "one"
-
- Scenario: Moving to end and selecting a word
- When I run :move-to-end-of-word
- And I run :toggle-selection
- And I run :move-to-end-of-word
- And I run :yank selection
- Then the clipboard should contain " two"
-
- Scenario: Moving to next word and selecting a word
- When I run :move-to-next-word
- And I run :toggle-selection
- And I run :move-to-end-of-word
- And I run :yank selection
- Then the clipboard should contain "two"
-
- Scenario: Moving to next word and selecting until next word
- When I run :move-to-next-word
- And I run :toggle-selection
- And I run :move-to-next-word
- And I run :yank selection
- Then the clipboard should contain "two "
-
- Scenario: Moving to previous word and selecting a word
- When I run :move-to-end-of-word
- And I run :toggle-selection
- And I run :move-to-prev-word
- And I run :yank selection
- Then the clipboard should contain "one"
-
- Scenario: Moving to previous word
- When I run :move-to-end-of-word
- And I run :move-to-prev-word
- And I run :toggle-selection
- And I run :move-to-end-of-word
- And I run :yank selection
- Then the clipboard should contain "one"
-
- # char
-
- Scenario: Selecting a char
- When I run :toggle-selection
- And I run :move-to-next-char
- And I run :yank selection
- Then the clipboard should contain "o"
-
- Scenario: Moving and selecting a char
- When I run :move-to-next-char
- And I run :toggle-selection
- And I run :move-to-next-char
- And I run :yank selection
- Then the clipboard should contain "n"
-
- Scenario: Selecting previous char
- When I run :move-to-end-of-word
- And I run :toggle-selection
- And I run :move-to-prev-char
- And I run :yank selection
- Then the clipboard should contain "e"
-
- Scenario: Moving to previous char
- When I run :move-to-end-of-word
- And I run :move-to-prev-char
- And I run :toggle-selection
- And I run :move-to-end-of-word
- And I run :yank selection
- Then the clipboard should contain "e"
-
# :yank selection
Scenario: :yank selection without selection
@@ -261,42 +43,8 @@ Feature: Caret mode
And the message "7 chars yanked to clipboard" should be shown.
And the clipboard should contain "one two"
- # :drop-selection
-
- Scenario: :drop-selection
- When I run :toggle-selection
- And I run :move-to-end-of-word
- And I run :drop-selection
- And I run :yank selection
- Then the message "Nothing to yank" should be shown.
-
# :follow-selected
- Scenario: :follow-selected without a selection
- When I run :follow-selected
- Then no crash should happen
-
- Scenario: :follow-selected with text
- When I run :move-to-next-word
- And I run :toggle-selection
- And I run :move-to-end-of-word
- And I run :follow-selected
- Then no crash should happen
-
- Scenario: :follow-selected with link (with JS)
- When I set content.javascript.enabled to true
- And I run :toggle-selection
- And I run :move-to-end-of-word
- And I run :follow-selected
- Then data/hello.txt should be loaded
-
- Scenario: :follow-selected with link (without JS)
- When I set content.javascript.enabled to false
- And I run :toggle-selection
- And I run :move-to-end-of-word
- And I run :follow-selected
- Then data/hello.txt should be loaded
-
Scenario: :follow-selected with --tab (with JS)
When I set content.javascript.enabled to true
And I run :tab-only
@@ -356,28 +104,3 @@ Feature: Caret mode
And I run :fake-key <tab>
And I run :follow-selected --tab
Then data/hello.txt should be loaded
-
- # Search + caret mode
-
- # https://bugreports.qt.io/browse/QTBUG-60673
- @qtbug60673
- Scenario: yanking a searched line
- When I run :leave-mode
- And I run :search fiv
- And I wait for "search found fiv" in the log
- And I run :enter-mode caret
- And I run :move-to-end-of-line
- And I run :yank selection
- Then the clipboard should contain "five six"
-
- @qtbug60673
- Scenario: yanking a searched line with multiple matches
- When I run :leave-mode
- And I run :search w
- And I wait for "search found w" in the log
- And I run :search-next
- And I wait for "next_result found w" in the log
- And I run :enter-mode caret
- And I run :move-to-end-of-line
- And I run :yank selection
- Then the clipboard should contain "wei drei"
diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py
index 5ce081e3b..5332d1433 100644
--- a/tests/helpers/fixtures.py
+++ b/tests/helpers/fixtures.py
@@ -31,6 +31,8 @@ import itertools
import textwrap
import unittest.mock
import types
+import mimetypes
+import os.path
import attr
import pytest
@@ -44,12 +46,15 @@ import helpers.utils
from qutebrowser.config import (config, configdata, configtypes, configexc,
configfiles, configcache)
from qutebrowser.utils import objreg, standarddir, utils, usertypes
-from qutebrowser.browser import greasemonkey, history
+from qutebrowser.browser import greasemonkey, history, qutescheme
from qutebrowser.browser.webkit import cookies
from qutebrowser.misc import savemanager, sql, objects
from qutebrowser.keyinput import modeman
+_qute_scheme_handler = None
+
+
class WinRegistryHelper:
"""Helper class for win_registry."""
@@ -152,29 +157,86 @@ def greasemonkey_manager(data_tmpdir):
objreg.delete('greasemonkey')
+@pytest.fixture(scope='session')
+def testdata_scheme(qapp):
+ try:
+ global _qute_scheme_handler
+ from qutebrowser.browser.webengine import webenginequtescheme
+ from PyQt5.QtWebEngineWidgets import QWebEngineProfile
+ _qute_scheme_handler = webenginequtescheme.QuteSchemeHandler(
+ parent=qapp)
+ _qute_scheme_handler.install(QWebEngineProfile.defaultProfile())
+ except ImportError:
+ pass
+
+ @qutescheme.add_handler('testdata')
+ def handler(url): # pylint: disable=unused-variable
+ file_abs = os.path.abspath(os.path.dirname(__file__))
+ filename = os.path.join(file_abs, os.pardir, 'end2end',
+ url.path().lstrip('/'))
+ with open(filename, 'rb') as f:
+ data = f.read()
+
+ mimetype, _encoding = mimetypes.guess_type(filename)
+ return mimetype, data
+
+
+@pytest.fixture
+def web_tab_setup(qtbot, tab_registry, session_manager_stub,
+ greasemonkey_manager, fake_args, host_blocker_stub,
+ config_stub, testdata_scheme):
+ """Shared setup for webkit_tab/webengine_tab."""
+ # Make sure error logging via JS fails tests
+ config_stub.val.content.javascript.log = {
+ 'info': 'info',
+ 'error': 'error',
+ 'unknown': 'error',
+ 'warning': 'error',
+ }
+
+
@pytest.fixture
-def webkit_tab(qtbot, tab_registry, cookiejar_and_cache, mode_manager,
- session_manager_stub, greasemonkey_manager, fake_args):
+def webkit_tab(web_tab_setup, qtbot, cookiejar_and_cache, mode_manager):
webkittab = pytest.importorskip('qutebrowser.browser.webkit.webkittab')
+
+ container = QWidget()
+ qtbot.add_widget(container)
+
+ vbox = QVBoxLayout(container)
tab = webkittab.WebKitTab(win_id=0, mode_manager=mode_manager,
private=False)
- qtbot.add_widget(tab)
+ vbox.addWidget(tab)
+ # to make sure container isn't GCed
+ tab.container = container
+
+ with qtbot.waitExposed(container):
+ container.show()
+
return tab
@pytest.fixture
-def webengine_tab(qtbot, tab_registry, fake_args, mode_manager,
- session_manager_stub, greasemonkey_manager,
- redirect_webengine_data, tabbed_browser_stubs):
+def webengine_tab(web_tab_setup, qtbot, redirect_webengine_data,
+ tabbed_browser_stubs, mode_manager):
tabwidget = tabbed_browser_stubs[0].widget
tabwidget.current_index = 0
tabwidget.index_of = 0
+ container = QWidget()
+ qtbot.add_widget(container)
+
+ vbox = QVBoxLayout(container)
webenginetab = pytest.importorskip(
'qutebrowser.browser.webengine.webenginetab')
tab = webenginetab.WebEngineTab(win_id=0, mode_manager=mode_manager,
private=False)
- qtbot.add_widget(tab)
+ vbox.addWidget(tab)
+ # to make sure container isn't GCed
+ tab.container = container
+
+ with qtbot.waitExposed(container):
+ container.show()
+
return tab
@@ -455,9 +517,8 @@ def fake_args(request):
@pytest.fixture
-def mode_manager(win_registry, config_stub, qapp):
- mm = modeman.ModeManager(0)
- objreg.register('mode-manager', mm, scope='window', window=0)
+def mode_manager(win_registry, config_stub, key_config_stub, qapp):
+ mm = modeman.init(0, parent=qapp)
yield mm
objreg.delete('mode-manager', scope='window', window=0)
diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py
index f10522a02..9af7a6fcc 100644
--- a/tests/helpers/stubs.py
+++ b/tests/helpers/stubs.py
@@ -475,6 +475,9 @@ class HostBlockerStub:
def __init__(self):
self.blocked_hosts = set()
+ def is_blocked(self, url):
+ return url in self.blocked_hosts
+
class SessionManagerStub:
diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py
index 06cf54467..66b3f8133 100644
--- a/tests/helpers/utils.py
+++ b/tests/helpers/utils.py
@@ -203,6 +203,7 @@ class CallbackChecker(QObject):
def check(self, expected):
"""Wait until the JS result arrived and compare it."""
+ __tracebackhide__ = True
if self._result is self.UNSET:
with self._qtbot.waitSignal(self.got_result, timeout=2000):
pass
diff --git a/tests/unit/browser/test_caret.py b/tests/unit/browser/test_caret.py
new file mode 100644
index 000000000..c93ff2afc
--- /dev/null
+++ b/tests/unit/browser/test_caret.py
@@ -0,0 +1,359 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 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/>.
+
+"""Tests for caret browsing mode."""
+
+import textwrap
+
+import pytest
+from PyQt5.QtCore import QUrl
+
+from qutebrowser.utils import usertypes
+from helpers import utils
+
+
+@pytest.fixture
+def caret(web_tab, qtbot, mode_manager):
+ with qtbot.wait_signal(web_tab.load_finished):
+ web_tab.openurl(QUrl('qute://testdata/data/caret.html'))
+
+ mode_manager.enter(usertypes.KeyMode.caret)
+
+ return web_tab.caret
+
+
+class Selection:
+
+ """Helper to interact with the caret selection."""
+
+ def __init__(self, qtbot, caret):
+ self._qtbot = qtbot
+ self._caret = caret
+ self._callback_checker = utils.CallbackChecker(qtbot)
+
+ def check(self, expected, *, strip=False):
+ """Check whether we got the expected selection.
+
+ Since (especially on Windows) the selection is empty if we're checking
+ too quickly, we try to read it multiple times.
+ """
+ for _ in range(10):
+ with self._qtbot.wait_signal(
+ self._callback_checker.got_result) as blocker:
+ self._caret.selection(self._callback_checker.callback)
+
+ selection = blocker.args[0]
+ if selection:
+ if strip:
+ selection = selection.strip()
+ assert selection == expected
+ return
+
+ self._qtbot.wait(50)
+
+ def check_multiline(self, expected, *, strip=False):
+ self.check(textwrap.dedent(expected).strip(), strip=strip)
+
+ def toggle(self):
+ with self._qtbot.wait_signal(self._caret.selection_toggled):
+ self._caret.toggle_selection()
+
+
+@pytest.fixture
+def selection(qtbot, caret, callback_checker):
+ return Selection(qtbot, caret)
+
+
+class TestDocument:
+
+ def test_selecting_entire_document(self, caret, selection):
+ selection.toggle()
+ caret.move_to_end_of_document()
+ selection.check_multiline("""
+ one two three
+ eins zwei drei
+
+ four five six
+ vier fünf sechs
+ """, strip=True)
+
+ def test_moving_to_end_and_start(self, caret, selection):
+ caret.move_to_end_of_document()
+ caret.move_to_start_of_document()
+ selection.toggle()
+ caret.move_to_end_of_word()
+ selection.check("one")
+
+ def test_moving_to_end_and_start_with_selection(self, caret, selection):
+ caret.move_to_end_of_document()
+ selection.toggle()
+ caret.move_to_start_of_document()
+ selection.check_multiline("""
+ one two three
+ eins zwei drei
+
+ four five six
+ vier fünf sechs
+ """, strip=True)
+
+
+class TestBlock:
+
+ def test_selecting_block(self, caret, selection):
+ selection.toggle()
+ caret.move_to_end_of_next_block()
+ selection.check_multiline("""
+ one two three
+ eins zwei drei
+ """)
+
+ def test_moving_back_to_the_end_of_prev_block_with_sel(self, caret, selection):
+ caret.move_to_end_of_next_block(2)
+ selection.toggle()
+ caret.move_to_end_of_prev_block()
+ caret.move_to_prev_word()
+ selection.check_multiline("""
+ drei
+
+ four five six
+ """)
+
+ def test_moving_back_to_the_end_of_prev_block(self, caret, selection):
+ caret.move_to_end_of_next_block(2)
+ caret.move_to_end_of_prev_block()
+ selection.toggle()
+ caret.move_to_prev_word()
+ selection.check("drei")
+
+ def test_moving_back_to_the_start_of_prev_block_with_sel(self, caret, selection):
+ caret.move_to_end_of_next_block(2)
+ selection.toggle()
+ caret.move_to_start_of_prev_block()
+ selection.check_multiline("""
+ eins zwei drei
+
+ four five six
+ """)
+
+ def test_moving_back_to_the_start_of_prev_block(self, caret, selection):
+ caret.move_to_end_of_next_block(2)
+ caret.move_to_start_of_prev_block()
+ selection.toggle()
+ caret.move_to_next_word()
+ selection.check("eins ")
+
+ def test_moving_to_the_start_of_next_block_with_sel(self, caret, selection):
+ selection.toggle()
+ caret.move_to_start_of_next_block()
+ selection.check("one two three\n")
+
+ def test_moving_to_the_start_of_next_block(self, caret, selection):
+ caret.move_to_start_of_next_block()
+ selection.toggle()
+ caret.move_to_end_of_word()
+ selection.check("eins")
+
+
+class TestLine:
+
+ def test_selecting_a_line(self, caret, selection):
+ selection.toggle()
+ caret.move_to_end_of_line()
+ selection.check("one two three")
+
+ def test_moving_and_selecting_a_line(self, caret, selection):
+ caret.move_to_next_line()
+ selection.toggle()
+ caret.move_to_end_of_line()
+ selection.check("eins zwei drei")
+
+ def test_selecting_next_line(self, caret, selection):
+ selection.toggle()
+ caret.move_to_next_line()
+ selection.check("one two three\n")
+
+ def test_moving_to_end_and_to_start_of_line(self, caret, selection):
+ caret.move_to_end_of_line()
+ caret.move_to_start_of_line()
+ selection.toggle()
+ caret.move_to_end_of_word()
+ selection.check("one")
+
+ def test_selecting_a_line_backwards(self, caret, selection):
+ caret.move_to_end_of_line()
+ selection.toggle()
+ caret.move_to_start_of_line()
+ selection.check("one two three")
+
+ def test_selecting_previous_line(self, caret, selection):
+ caret.move_to_next_line()
+ selection.toggle()
+ caret.move_to_prev_line()
+ selection.check("one two three\n")
+
+ def test_moving_to_previous_line(self, caret, selection):
+ caret.move_to_next_line()
+ caret.move_to_prev_line()
+ selection.toggle()
+ caret.move_to_next_line()
+ selection.check("one two three\n")
+
+
+class TestWord:
+
+ def test_selecting_a_word(self, caret, selection):
+ selection.toggle()
+ caret.move_to_end_of_word()
+ selection.check("one")
+
+ def test_moving_to_end_and_selecting_a_word(self, caret, selection):
+ caret.move_to_end_of_word()
+ selection.toggle()
+ caret.move_to_end_of_word()
+ selection.check(" two")
+
+ def test_moving_to_next_word_and_selecting_a_word(self, caret, selection):
+ caret.move_to_next_word()
+ selection.toggle()
+ caret.move_to_end_of_word()
+ selection.check("two")
+
+ def test_moving_to_next_word_and_selecting_until_next_word(self, caret, selection):
+ caret.move_to_next_word()
+ selection.toggle()
+ caret.move_to_next_word()
+ selection.check("two ")
+
+ def test_moving_to_previous_word_and_selecting_a_word(self, caret, selection):
+ caret.move_to_end_of_word()
+ selection.toggle()
+ caret.move_to_prev_word()
+ selection.check("one")
+
+ def test_moving_to_previous_word(self, caret, selection):
+ caret.move_to_end_of_word()
+ caret.move_to_prev_word()
+ selection.toggle()
+ caret.move_to_end_of_word()
+ selection.check("one")
+
+
+class TestChar:
+
+ def test_selecting_a_char(self, caret, selection):
+ selection.toggle()
+ caret.move_to_next_char()
+ selection.check("o")
+
+ def test_moving_and_selecting_a_char(self, caret, selection):
+ caret.move_to_next_char()
+ selection.toggle()
+ caret.move_to_next_char()
+ selection.check("n")
+
+ def test_selecting_previous_char(self, caret, selection):
+ caret.move_to_end_of_word()
+ selection.toggle()
+ caret.move_to_prev_char()
+ selection.check("e")
+
+ def test_moving_to_previous_char(self, caret, selection):
+ caret.move_to_end_of_word()
+ caret.move_to_prev_char()
+ selection.toggle()
+ caret.move_to_end_of_word()
+ selection.check("e")
+
+
+def test_drop_selection(caret, selection):
+ selection.toggle()
+ caret.move_to_end_of_word()
+ caret.drop_selection()
+ selection.check("")
+
+
+class TestSearch:
+
+ # https://bugreports.qt.io/browse/QTBUG-60673
+
+ @pytest.mark.qtbug60673
+ @pytest.mark.no_xvfb
+ def test_yanking_a_searched_line(self, caret, selection, mode_manager, callback_checker, web_tab, qtbot):
+ web_tab.show()
+ mode_manager.leave(usertypes.KeyMode.caret)
+
+ web_tab.search.search('fiv', result_cb=callback_checker.callback)
+ callback_checker.check(True)
+
+ mode_manager.enter(usertypes.KeyMode.caret)
+ caret.move_to_end_of_line()
+ selection.check('five six')
+
+ @pytest.mark.qtbug60673
+ @pytest.mark.no_xvfb
+ def test_yanking_a_searched_line_with_multiple_matches(self, caret, selection, mode_manager, callback_checker, web_tab, qtbot):
+ web_tab.show()
+ mode_manager.leave(usertypes.KeyMode.caret)
+
+ web_tab.search.search('w', result_cb=callback_checker.callback)
+ callback_checker.check(True)
+
+ web_tab.search.next_result(result_cb=callback_checker.callback)
+ callback_checker.check(True)
+
+ mode_manager.enter(usertypes.KeyMode.caret)
+
+ caret.move_to_end_of_line()
+ selection.check('wei drei')
+
+
+class TestFollowSelected:
+
+ LOAD_STARTED_DELAY = 50
+
+ @pytest.fixture(params=[True, False], autouse=True)
+ def toggle_js(self, request, config_stub):
+ config_stub.val.content.javascript.enabled = request.param
+
+ def test_follow_selected_without_a_selection(self, qtbot, caret, selection, web_tab,
+ mode_manager):
+ caret.move_to_next_word() # Move cursor away from the link
+ mode_manager.leave(usertypes.KeyMode.caret)
+ with qtbot.wait_signal(caret.follow_selected_done):
+ with qtbot.assert_not_emitted(web_tab.load_started):
+ caret.follow_selected()
+ qtbot.wait(self.LOAD_STARTED_DELAY)
+
+ def test_follow_selected_with_text(self, qtbot, caret, selection, web_tab):
+ caret.move_to_next_word()
+ selection.toggle()
+ caret.move_to_end_of_word()
+ with qtbot.wait_signal(caret.follow_selected_done):
+ with qtbot.assert_not_emitted(web_tab.load_started):
+ caret.follow_selected()
+ qtbot.wait(self.LOAD_STARTED_DELAY)
+
+ def test_follow_selected_with_link(self, caret, selection, config_stub,
+ qtbot, web_tab):
+ selection.toggle()
+ caret.move_to_end_of_word()
+ with qtbot.wait_signal(web_tab.load_finished):
+ with qtbot.wait_signal(caret.follow_selected_done):
+ caret.follow_selected()
+ assert web_tab.url().path() == '/data/hello.txt'
diff --git a/tests/unit/utils/test_javascript.py b/tests/unit/utils/test_javascript.py
index 29e090fd0..0a196cfa1 100644
--- a/tests/unit/utils/test_javascript.py
+++ b/tests/unit/utils/test_javascript.py
@@ -84,6 +84,7 @@ class TestStringEscape:
(None, 'undefined'),
(object(), TypeError),
(True, 'true'),
+ ([23, True, 'x'], '[23, true, "x"]'),
])
def test_convert_js_arg(arg, expected):
if expected is TypeError:
diff --git a/tox.ini b/tox.ini
index 02f3ae729..37451918c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -16,7 +16,7 @@ setenv =
pyqt{,56,571,59,510,511}: LINK_PYQT_SKIP=true
pyqt{,56,571,59,510,511}: QUTE_BDD_WEBENGINE=true
cov: PYTEST_ADDOPTS=--cov --cov-report xml --cov-report=html --cov-report=
-passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI TRAVIS XDG_* QUTE_* DOCKER
+passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI TRAVIS XDG_* QUTE_* DOCKER QT_QUICK_BACKEND
basepython =
py35: {env:PYTHON:python3.5}
py36: {env:PYTHON:python3.6}