summaryrefslogtreecommitdiff
path: root/tests/integration
diff options
context:
space:
mode:
Diffstat (limited to 'tests/integration')
-rw-r--r--tests/integration/data/downloads/ä-issue908.binbin0 -> 1 bytes
-rw-r--r--tests/integration/data/marks.html16
-rw-r--r--tests/integration/data/title with spaces.html10
-rw-r--r--tests/integration/features/conftest.py18
-rw-r--r--tests/integration/features/downloads.feature20
-rw-r--r--tests/integration/features/editor.feature72
-rw-r--r--tests/integration/features/hints.feature7
-rw-r--r--tests/integration/features/marks.feature91
-rw-r--r--tests/integration/features/misc.feature7
-rw-r--r--tests/integration/features/spawn.feature13
-rw-r--r--tests/integration/features/tabs.feature67
-rw-r--r--tests/integration/features/test_downloads.py13
-rw-r--r--tests/integration/features/test_editor.py45
-rw-r--r--tests/integration/features/test_marks.py29
-rw-r--r--tests/integration/features/yankpaste.feature12
-rw-r--r--tests/integration/quteprocess.py38
-rw-r--r--tests/integration/test_invocations.py (renamed from tests/integration/test_cmdline_args.py)22
-rw-r--r--tests/integration/test_quteprocess.py10
-rw-r--r--tests/integration/test_smoke.py14
-rw-r--r--tests/integration/testprocess.py74
20 files changed, 525 insertions, 53 deletions
diff --git a/tests/integration/data/downloads/ä-issue908.bin b/tests/integration/data/downloads/ä-issue908.bin
new file mode 100644
index 000000000..f76dd238a
--- /dev/null
+++ b/tests/integration/data/downloads/ä-issue908.bin
Binary files differ
diff --git a/tests/integration/data/marks.html b/tests/integration/data/marks.html
new file mode 100644
index 000000000..574e2cdd1
--- /dev/null
+++ b/tests/integration/data/marks.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Marks I</title>
+ </head>
+ <body>
+ <h1 id="top">Top</h1>
+ <a href="#top">Top</a>
+ <a href="#bottom">Bottom</a>
+ <div style="height: 3000px; width: 3000px;">Holy Grail</div>
+ <div style="height: 3000px; width: 3000px;">Waldo</div>
+ <div style="height: 3000px; width: 3000px;">Holy Grail</div>
+ <h1 id="bottom">Bottom</h1>
+ </body>
+</html>
diff --git a/tests/integration/data/title with spaces.html b/tests/integration/data/title with spaces.html
new file mode 100644
index 000000000..d5e2ab9da
--- /dev/null
+++ b/tests/integration/data/title with spaces.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Test title</title>
+ </head>
+ <body>
+ foo
+ </body>
+</html>
diff --git a/tests/integration/features/conftest.py b/tests/integration/features/conftest.py
index a099a1887..863cc1a25 100644
--- a/tests/integration/features/conftest.py
+++ b/tests/integration/features/conftest.py
@@ -28,7 +28,6 @@ import collections
import textwrap
import pytest
-import yaml
import pytest_bdd as bdd
from helpers import utils
@@ -180,6 +179,7 @@ def wait_in_log(quteproc, is_regex, pattern, do_skip):
r'"(?P<message>.*)"'))
def wait_for_message(quteproc, httpbin, category, message):
"""Wait for a given statusbar message/error/warning."""
+ quteproc.log_summary('Waiting for {} "{}"'.format(category, message))
expect_message(quteproc, httpbin, category, message)
@@ -275,10 +275,12 @@ def expect_message(quteproc, httpbin, category, message):
@bdd.then(bdd.parsers.re(r'(?P<is_regex>regex )?"(?P<pattern>[^"]+)" should '
r'be logged'))
-def should_be_logged(quteproc, is_regex, pattern):
+def should_be_logged(quteproc, httpbin, is_regex, pattern):
"""Expect the given pattern on regex in the log."""
if is_regex:
pattern = re.compile(pattern)
+ else:
+ pattern = pattern.replace('(port)', str(httpbin.port))
line = quteproc.wait_for(message=pattern)
line.expected = True
@@ -312,21 +314,15 @@ def compare_session(quteproc, expected):
partial_compare is used, which means only the keys/values listed will be
compared.
"""
- # Translate ... to ellipsis in YAML.
- loader = yaml.SafeLoader(expected)
- loader.add_constructor('!ellipsis', lambda loader, node: ...)
- loader.add_implicit_resolver('!ellipsis', re.compile(r'\.\.\.'), None)
-
- data = quteproc.get_session()
- expected = loader.get_data()
- assert utils.partial_compare(data, expected)
+ quteproc.compare_session(expected)
@bdd.then("no crash should happen")
def no_crash():
"""Don't do anything.
- This is actually a NOP as a crash is already checked in the log."""
+ This is actually a NOP as a crash is already checked in the log.
+ """
pass
diff --git a/tests/integration/features/downloads.feature b/tests/integration/features/downloads.feature
index 8097711a5..fc401fe76 100644
--- a/tests/integration/features/downloads.feature
+++ b/tests/integration/features/downloads.feature
@@ -162,6 +162,26 @@ Feature: Downloading things from a website.
And I run :download-open with count 1
Then the error "Download 1 is not done!" should be shown
+ ## completion -> download-path-suggestion
+
+ Scenario: completion -> download-path-suggestion = path
+ When I set storage -> prompt-download-directory to true
+ And I set completion -> download-path-suggestion to path
+ And I open data/downloads/download.bin
+ Then the download prompt should be shown with "{downloaddir}/"
+
+ Scenario: completion -> download-path-suggestion = filename
+ When I set storage -> prompt-download-directory to true
+ And I set completion -> download-path-suggestion to filename
+ And I open data/downloads/download.bin
+ Then the download prompt should be shown with "download.bin"
+
+ Scenario: completion -> download-path-suggestion = both
+ When I set storage -> prompt-download-directory to true
+ And I set completion -> download-path-suggestion to both
+ And I open data/downloads/download.bin
+ Then the download prompt should be shown with "{downloaddir}/download.bin"
+
## https://github.com/The-Compiler/qutebrowser/issues/1242
Scenario: Closing window with remove-finished-downloads timeout
diff --git a/tests/integration/features/editor.feature b/tests/integration/features/editor.feature
new file mode 100644
index 000000000..ef3798564
--- /dev/null
+++ b/tests/integration/features/editor.feature
@@ -0,0 +1,72 @@
+Feature: Opening external editors
+
+ ## :edit-url
+
+ Scenario: Editing an URL
+ When I open data/numbers/1.txt
+ And I set up a fake editor replacing "1.txt" by "2.txt"
+ And I run :edit-url
+ Then data/numbers/2.txt should be loaded
+
+ Scenario: Editing an URL with -t
+ When I run :tab-only
+ And I open data/numbers/1.txt
+ And I set up a fake editor replacing "1.txt" by "2.txt"
+ And I run :edit-url -t
+ Then data/numbers/2.txt should be loaded
+ And the following tabs should be open:
+ - data/numbers/1.txt
+ - data/numbers/2.txt (active)
+
+ Scenario: Editing an URL with -b
+ When I run :tab-only
+ And I open data/numbers/1.txt
+ And I set up a fake editor replacing "1.txt" by "2.txt"
+ And I run :edit-url -b
+ Then data/numbers/2.txt should be loaded
+ And the following tabs should be open:
+ - data/numbers/1.txt (active)
+ - data/numbers/2.txt
+
+ Scenario: Editing an URL with -w
+ When I open data/numbers/1.txt in a new tab
+ And I run :tab-only
+ And I set up a fake editor replacing "1.txt" by "2.txt"
+ And I run :edit-url -w
+ Then data/numbers/2.txt should be loaded
+ And the session should look like:
+ windows:
+ - tabs:
+ - active: true
+ history:
+ - active: true
+ url: http://localhost:*/data/numbers/1.txt
+ - tabs:
+ - active: true
+ history:
+ - active: true
+ url: http://localhost:*/data/numbers/2.txt
+
+ Scenario: Editing an URL with count
+ Given I have a fresh instance
+ When I open data/numbers/1.txt
+ And I run :tab-only
+ And I open about:blank in a new tab
+ And I run :tab-focus 1
+ And I set up a fake editor replacing "1.txt" by "2.txt"
+ And I run :edit-url with count 2
+ Then data/numbers/2.txt should be loaded
+ And the following tabs should be open:
+ - data/numbers/1.txt (active)
+ - data/numbers/2.txt
+
+ Scenario: Editing an URL with -t and -b
+ When I run :edit-url -t -b
+ Then the error "Only one of -t/-b/-w can be given!" should be shown
+
+ Scenario: Editing an URL with invalid URL
+ When I set general -> auto-search to false
+ And I open data/hello.txt
+ And I set up a fake editor replacing "http://localhost:(port)/data/hello.txt" by "foo!"
+ And I run :edit-url
+ Then the error "Invalid URL" should be shown
diff --git a/tests/integration/features/hints.feature b/tests/integration/features/hints.feature
index 3f6fab924..062de293c 100644
--- a/tests/integration/features/hints.feature
+++ b/tests/integration/features/hints.feature
@@ -44,3 +44,10 @@ Feature: Using hints
And I wait until data/hello.txt is loaded
Then the following tabs should be open:
- data/hello.txt (active)
+
+ @xfail
+ Scenario: Using :hint spawn with flags (issue 797)
+ When I open data/hints/link.html
+ And I run :hint all spawn -v echo
+ And I run :follow-hint a
+ Then the message "Command exited successfully" should be shown
diff --git a/tests/integration/features/marks.feature b/tests/integration/features/marks.feature
new file mode 100644
index 000000000..3a35791b8
--- /dev/null
+++ b/tests/integration/features/marks.feature
@@ -0,0 +1,91 @@
+Feature: Setting positional marks
+
+ Background:
+ Given I open data/marks.html
+ And I run :tab-only
+
+ ## :set-mark, :jump-mark
+
+ Scenario: Setting and jumping to a local mark
+ When I run :scroll-px 5 10
+ And I run :set-mark a
+ And I run :scroll-px 0 20
+ And I run :jump-mark a
+ Then the page should be scrolled to 5 10
+
+ Scenario: Jumping back after jumping to a particular percentage
+ When I run :scroll-px 10 20
+ And I run :scroll-perc 100
+ And I run :jump-mark "'"
+ Then the page should be scrolled to 10 20
+
+ Scenario: Setting the same local mark on another page
+ When I run :scroll-px 5 10
+ And I run :set-mark a
+ And I open data/marks.html
+ And I run :scroll-px 0 20
+ And I run :set-mark a
+ And I run :jump-mark a
+ Then the page should be scrolled to 0 20
+
+ Scenario: Jumping to a local mark after returning to a page
+ When I run :scroll-px 5 10
+ And I run :set-mark a
+ And I open data/numbers/1.txt
+ And I run :set-mark a
+ And I open data/marks.html
+ And I run :jump-mark a
+ Then the page should be scrolled to 5 10
+
+ Scenario: Setting and jumping to a global mark
+ When I run :scroll-px 5 20
+ And I run :set-mark A
+ And I open data/numbers/1.txt
+ And I run :jump-mark A
+ Then data/marks.html should be loaded
+ And the page should be scrolled to 5 20
+
+ Scenario: Jumping to an unset mark
+ When I run :jump-mark b
+ Then the error "Mark b is not set" should be shown
+
+ Scenario: Jumping to a local mark that was set on another page
+ When I run :set-mark b
+ And I open data/numbers/1.txt
+ And I run :jump-mark b
+ Then the error "Mark b is not set" should be shown
+
+ Scenario: Jumping to a local mark after changing fragments
+ When I open data/marks.html#top
+ And I run :scroll 'top'
+ And I run :scroll-px 10 10
+ And I run :set-mark a
+ When I open data/marks.html#bottom
+ And I run :jump-mark a
+ Then the page should be scrolled to 10 10
+
+ Scenario: Jumping back after following a link
+ When I run :hint links normal
+ And I run :follow-hint s
+ And I run :jump-mark "'"
+ Then the page should be scrolled to 0 0
+
+ Scenario: Jumping back after searching
+ When I run :scroll-px 20 15
+ And I run :search Waldo
+ And I run :jump-mark "'"
+ Then the page should be scrolled to 20 15
+
+ Scenario: Jumping back after search-next
+ When I run :search Grail
+ And I run :search-next
+ And I run :jump-mark "'"
+ Then the page should be scrolled to 0 0
+
+ Scenario: Hovering a hint does not set the ' mark
+ When I run :scroll-px 30 20
+ And I run :scroll-perc 0
+ And I run :hint links hover
+ And I run :follow-hint s
+ And I run :jump-mark "'"
+ Then the page should be scrolled to 30 20
diff --git a/tests/integration/features/misc.feature b/tests/integration/features/misc.feature
index 8f0ab8014..991560b7a 100644
--- a/tests/integration/features/misc.feature
+++ b/tests/integration/features/misc.feature
@@ -362,3 +362,10 @@ Feature: Various utility commands.
And I press the key "<Tab>"
And I press the key "<Ctrl-C>"
Then no crash should happen
+
+ ## Custom headers
+
+ Scenario: Setting a custom header
+ When I set network -> custom-headers to {"X-Qute-Test": "testvalue"}
+ And I open headers
+ Then the header X-Qute-Test should be set to testvalue
diff --git a/tests/integration/features/spawn.feature b/tests/integration/features/spawn.feature
index 03506c18b..861091f91 100644
--- a/tests/integration/features/spawn.feature
+++ b/tests/integration/features/spawn.feature
@@ -16,9 +16,20 @@ Feature: :spawn
When I run :spawn echo {url}
Then "Executing echo with args ['about:blank'], userscript=False" should be logged
+ Scenario: Running :spawn with url variable in fully encoded format
+ When I open data/title with spaces.html
+ And I run :spawn echo {url}
+ Then "Executing echo with args ['http://localhost:(port)/data/title%20with%20spaces.html'], userscript=False" should be logged
+
+ Scenario: Running :spawn with url variable in pretty decoded format
+ When I open data/title with spaces.html
+ And I run :spawn echo {url:pretty}
+ Then "Executing echo with args ['http://localhost:(port)/data/title with spaces.html'], userscript=False" should be logged
+
@posix
Scenario: Running :spawn with userscript
- When I execute the userscript open_current_url
+ When I open about:blank
+ And I execute the userscript open_current_url
And I wait until about:blank is loaded
Then the following tabs should be open:
- about:blank
diff --git a/tests/integration/features/tabs.feature b/tests/integration/features/tabs.feature
index 630fa6279..cb7b54da8 100644
--- a/tests/integration/features/tabs.feature
+++ b/tests/integration/features/tabs.feature
@@ -228,6 +228,34 @@ Feature: Tab management
- data/numbers/2.txt
- data/numbers/3.txt
+ Scenario: :tab-focus with -1
+ When I open data/numbers/1.txt
+ And I open data/numbers/2.txt in a new tab
+ And I open data/numbers/3.txt in a new tab
+ And I run :tab-focus 1
+ And I run :tab-focus -1
+ Then the following tabs should be open:
+ - data/numbers/1.txt
+ - data/numbers/2.txt
+ - data/numbers/3.txt (active)
+
+ Scenario: :tab-focus negative index
+ When I open data/numbers/1.txt
+ And I open data/numbers/2.txt in a new tab
+ And I open data/numbers/3.txt in a new tab
+ And I run :tab-focus -2
+ Then the following tabs should be open:
+ - data/numbers/1.txt
+ - data/numbers/2.txt (active)
+ - data/numbers/3.txt
+
+ Scenario: :tab-focus with invalid negative index
+ When I open data/numbers/1.txt
+ And I open data/numbers/2.txt in a new tab
+ And I open data/numbers/3.txt in a new tab
+ And I run :tab-focus -5
+ Then the error "There's no tab with index -1!" should be shown
+
Scenario: :tab-focus last with no last focused tab
Given I have a fresh instance
And I run :tab-focus last
@@ -712,12 +740,12 @@ Feature: Tab management
# :buffer
- Scenario: buffer without args
+ Scenario: :buffer without args
Given I have a fresh instance
When I run :buffer
Then the error "buffer: The following arguments are required: index" should be shown
- Scenario: buffer one window title present
+ Scenario: :buffer with a matching title
When I open data/title.html
And I open data/search.html in a new tab
And I open data/scroll.html in a new tab
@@ -727,11 +755,11 @@ Feature: Tab management
- data/search.html (active)
- data/scroll.html
- Scenario: buffer one window title not present
+ Scenario: :buffer with no matching title
When I run :buffer "invalid title"
Then the error "No matching tab for: invalid title" should be shown
- Scenario: buffer two window title present
+ Scenario: :buffer with matching title and two windows
When I open data/title.html
And I open data/search.html in a new tab
And I open data/scroll.html in a new tab
@@ -757,17 +785,17 @@ Feature: Tab management
history:
- url: http://localhost:*/data/paste_primary.html
- Scenario: buffer one window index not present
+ Scenario: :buffer with no matching index
When I open data/title.html
And I run :buffer "666"
Then the error "There's no tab with index 666!" should be shown
- Scenario: buffer one window win not present
+ Scenario: :buffer with no matching window index
When I open data/title.html
And I run :buffer "2/1"
Then the error "There's no window with id 2!" should be shown
- Scenario: buffer two window index present
+ Scenario: :buffer with matching window index
Given I have a fresh instance
When I open data/title.html
And I open data/search.html in a new tab
@@ -776,6 +804,7 @@ Feature: Tab management
And I open data/paste_primary.html in a new tab
And I wait until data/caret.html is loaded
And I run :buffer "0/2"
+ And I wait for "Current tab changed, *" in the log
Then the session should look like:
windows:
- active: true
@@ -795,30 +824,44 @@ Feature: Tab management
history:
- url: http://localhost:*/data/paste_primary.html
- Scenario: buffer troubling args 01
+ Scenario: :buffer with wrong argument (-1)
Given I have a fresh instance
When I open data/title.html
And I run :buffer "-1"
Then the error "There's no tab with index -1!" should be shown
- Scenario: buffer troubling args 02
+ Scenario: :buffer with wrong argument (/)
When I open data/title.html
And I run :buffer "/"
Then the following tabs should be open:
- data/title.html (active)
- Scenario: buffer troubling args 03
+ Scenario: :buffer with wrong argument (//)
When I open data/title.html
And I run :buffer "//"
Then the following tabs should be open:
- data/title.html (active)
- Scenario: buffer troubling args 04
+ Scenario: :buffer with wrong argument (0/x)
When I open data/title.html
And I run :buffer "0/x"
Then the error "No matching tab for: 0/x" should be shown
- Scenario: buffer troubling args 05
+ Scenario: :buffer with wrong argument (1/2/3)
When I open data/title.html
And I run :buffer "1/2/3"
Then the error "No matching tab for: 1/2/3" should be shown
+
+ Scenario: Using :tab-next after closing last tab (#1448)
+ When I set tabs -> last-close to close
+ And I run :tab-only
+ And I run :tab-close ;; :tab-next
+ Then qutebrowser should quit
+ And no crash should happen
+
+ Scenario: Using :tab-prev after closing last tab (#1448)
+ When I set tabs -> last-close to close
+ And I run :tab-only
+ And I run :tab-close ;; :tab-prev
+ Then qutebrowser should quit
+ And no crash should happen
diff --git a/tests/integration/features/test_downloads.py b/tests/integration/features/test_downloads.py
index 51613dc61..36e32a758 100644
--- a/tests/integration/features/test_downloads.py
+++ b/tests/integration/features/test_downloads.py
@@ -17,6 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
+import os
+
import pytest_bdd as bdd
bdd.scenarios('downloads.feature')
@@ -49,3 +51,14 @@ def download_should_not_exist(filename, tmpdir):
def download_should_exist(filename, tmpdir):
path = tmpdir / filename
assert path.check()
+
+
+@bdd.then(bdd.parsers.parse('The download prompt should be shown with '
+ '"{path}"'))
+def download_prompt(tmpdir, quteproc, path):
+ full_path = path.replace('{downloaddir}', str(tmpdir)).replace('/', os.sep)
+ msg = ("Asking question <qutebrowser.utils.usertypes.Question "
+ "default={full_path!r} mode=<PromptMode.text: 2> "
+ "text='Save file to:'>, *".format(full_path=full_path))
+ quteproc.wait_for(message=msg)
+ quteproc.send_cmd(':leave-mode')
diff --git a/tests/integration/features/test_editor.py b/tests/integration/features/test_editor.py
new file mode 100644
index 000000000..7b400705f
--- /dev/null
+++ b/tests/integration/features/test_editor.py
@@ -0,0 +1,45 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2016 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 sys
+import textwrap
+
+import pytest_bdd as bdd
+bdd.scenarios('editor.feature')
+
+
+@bdd.when(bdd.parsers.parse('I set up a fake editor replacing "{text}" by '
+ '"{replacement}"'))
+def set_up_editor_replacement(quteproc, httpbin, tmpdir, text, replacement):
+ """Set up general->editor to a small python script doing a replacement."""
+ text = text.replace('(port)', str(httpbin.port))
+ script = tmpdir / 'script.py'
+ script.write(textwrap.dedent("""
+ import sys
+
+ with open(sys.argv[1], encoding='utf-8') as f:
+ data = f.read()
+
+ data = data.replace("{text}", "{replacement}")
+
+ with open(sys.argv[1], 'w', encoding='utf-8') as f:
+ f.write(data)
+ """.format(text=text, replacement=replacement)))
+ editor = '"{}" "{}" {{}}'.format(sys.executable, script)
+ quteproc.set_setting('general', 'editor', editor)
diff --git a/tests/integration/features/test_marks.py b/tests/integration/features/test_marks.py
new file mode 100644
index 000000000..b2777cbe4
--- /dev/null
+++ b/tests/integration/features/test_marks.py
@@ -0,0 +1,29 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2016 Ryan Roden-Corrent (rcorre) <ryan@rcorre.net>
+#
+# 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_bdd as bdd
+bdd.scenarios('marks.feature')
+
+
+@bdd.then(bdd.parsers.parse("the page should be scrolled to {x} {y}"))
+def check_y(quteproc, x, y):
+ data = quteproc.get_session()
+ pos = data['windows'][0]['tabs'][0]['history'][-1]['scroll-pos']
+ assert int(x) == pos['x']
+ assert int(y) == pos['y']
diff --git a/tests/integration/features/yankpaste.feature b/tests/integration/features/yankpaste.feature
index 8f6c6552f..58ae304b0 100644
--- a/tests/integration/features/yankpaste.feature
+++ b/tests/integration/features/yankpaste.feature
@@ -33,6 +33,18 @@ Feature: Yanking and pasting.
Then the message "Yanked domain to clipboard: http://localhost:(port)" should be shown
And the clipboard should contain "http://localhost:(port)"
+ Scenario: Yanking fully encoded URL
+ When I open data/title with spaces.html
+ And I run :yank
+ Then the message "Yanked URL to clipboard: http://localhost:(port)/data/title%20with%20spaces.html" should be shown
+ And the clipboard should contain "http://localhost:(port)/data/title%20with%20spaces.html"
+
+ Scenario: Yanking pretty decoded URL
+ When I open data/title with spaces.html
+ And I run :yank --pretty
+ Then the message "Yanked URL to clipboard: http://localhost:(port)/data/title with spaces.html" should be shown
+ And the clipboard should contain "http://localhost:(port)/data/title with spaces.html"
+
#### :paste
Scenario: Pasting an URL
diff --git a/tests/integration/quteprocess.py b/tests/integration/quteprocess.py
index 6154cfa5f..c7fa66732 100644
--- a/tests/integration/quteprocess.py
+++ b/tests/integration/quteprocess.py
@@ -320,6 +320,11 @@ class QuteProc(testprocess.Process):
def send_cmd(self, command, count=None):
"""Send a command to the running qutebrowser instance."""
+ summary = command
+ if count is not None:
+ summary += ' (count {})'.format(count)
+ self.log_summary(summary)
+
assert self._ipc_socket is not None
time.sleep(self._delay / 1000)
@@ -340,6 +345,8 @@ class QuteProc(testprocess.Process):
return msg.message.split(' = ')[1]
def set_setting(self, sect, opt, value):
+ # " in a value should be treated literally, so escape it
+ value = value.replace('"', '\\"')
self.send_cmd(':set "{}" "{}" "{}"'.format(sect, opt, value))
self.wait_for(category='config', message='Config option changed: *')
@@ -378,6 +385,8 @@ class QuteProc(testprocess.Process):
def wait_for_load_finished(self, path, *, port=None, https=False,
timeout=None, load_status='success'):
"""Wait until any tab has finished loading."""
+ __tracebackhide__ = True
+
if timeout is None:
if 'CI' in os.environ:
timeout = 15000
@@ -393,7 +402,12 @@ class QuteProc(testprocess.Process):
r"tab_id=\d+ url='{url}/?'>: LoadStatus\.{load_status}|fetch: "
r"PyQt5\.QtCore\.QUrl\('{url}'\) -> .*)".format(
load_status=re.escape(load_status), url=re.escape(url)))
- self.wait_for(message=pattern, timeout=timeout)
+
+ try:
+ self.wait_for(message=pattern, timeout=timeout)
+ except testprocess.WaitForTimeout:
+ raise testprocess.WaitForTimeout("Timed out while waiting for {} "
+ "to be loaded".format(url))
def get_session(self):
"""Save the session and get the parsed session data."""
@@ -405,7 +419,7 @@ class QuteProc(testprocess.Process):
with open(session, encoding='utf-8') as f:
data = f.read()
- self._log(data)
+ self._log('\nCurrent session data:\n' + data)
return yaml.load(data)
def get_content(self, plain=True):
@@ -450,6 +464,26 @@ class QuteProc(testprocess.Process):
raise ValueError('Invalid response from qutebrowser: {}'
.format(message))
+ def compare_session(self, expected):
+ """Compare the current sessions against the given template.
+
+ partial_compare is used, which means only the keys/values listed will
+ be compared.
+ """
+ __tracebackhide__ = True
+ # Translate ... to ellipsis in YAML.
+ loader = yaml.SafeLoader(expected)
+ loader.add_constructor('!ellipsis', lambda loader, node: ...)
+ loader.add_implicit_resolver('!ellipsis', re.compile(r'\.\.\.'), None)
+
+ data = self.get_session()
+ expected = loader.get_data()
+ outcome = testutils.partial_compare(data, expected)
+ if not outcome:
+ msg = "Session comparison failed: {}".format(outcome.error)
+ msg += '\nsee stdout for details'
+ pytest.fail(msg)
+
def _xpath_escape(text):
"""Escape a string to be used in an XPath expression.
diff --git a/tests/integration/test_cmdline_args.py b/tests/integration/test_invocations.py
index c88462207..a13c6ac25 100644
--- a/tests/integration/test_cmdline_args.py
+++ b/tests/integration/test_invocations.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
-"""Test starting qutebrowser with various commandline arguments."""
+"""Test starting qutebrowser with special arguments/environments."""
import pytest
@@ -51,3 +51,23 @@ def test_no_config(tmpdir, quteproc_new):
quteproc_new.start(args, env=env)
quteproc_new.send_cmd(':quit')
quteproc_new.wait_for_quit()
+
+
+@pytest.mark.linux
+def test_ascii_locale(httpbin, tmpdir, quteproc_new):
+ """Test downloads with LC_ALL=C set.
+
+ https://github.com/The-Compiler/qutebrowser/issues/908
+ """
+ args = ['--debug', '--no-err-windows', '--temp-basedir', 'about:blank']
+ quteproc_new.start(args, env={'LC_ALL': 'C'})
+ quteproc_new.set_setting('storage', 'download-directory', str(tmpdir))
+ quteproc_new.set_setting('storage', 'prompt-download-directory', 'false')
+ url = 'http://localhost:{port}/data/downloads/ä-issue908.bin'.format(
+ port=httpbin.port)
+ quteproc_new.send_cmd(':download {}'.format(url))
+ quteproc_new.send_cmd(':quit')
+ quteproc_new.wait_for_quit()
+
+ assert len(tmpdir.listdir()) == 1
+ assert (tmpdir / '?-issue908.bin').exists()
diff --git a/tests/integration/test_quteprocess.py b/tests/integration/test_quteprocess.py
index 7698e1a07..5110324ef 100644
--- a/tests/integration/test_quteprocess.py
+++ b/tests/integration/test_quteprocess.py
@@ -195,3 +195,13 @@ class TestClickElement:
])
def test_xpath_escape(string, expected):
assert quteprocess._xpath_escape(string) == expected
+
+
+@pytest.mark.parametrize('value', [
+ 'foo',
+ 'foo"bar', # Make sure a " is preserved
+])
+def test_set(quteproc, value):
+ quteproc.set_setting('network', 'accept-language', value)
+ read_back = quteproc.get_setting('network', 'accept-language')
+ assert read_back == value
diff --git a/tests/integration/test_smoke.py b/tests/integration/test_smoke.py
index 4d4f23b3f..cb4b600dc 100644
--- a/tests/integration/test_smoke.py
+++ b/tests/integration/test_smoke.py
@@ -19,10 +19,12 @@
"""Test which simply runs qutebrowser to check if it starts properly."""
-
import sys
import os.path
import subprocess
+import signal
+
+import pytest
def test_smoke():
@@ -32,7 +34,15 @@ def test_smoke():
argv = [sys.executable, '-m', 'qutebrowser']
argv += ['--debug', '--no-err-windows', '--nowindow', '--temp-basedir',
'about:blank', ':later 500 quit']
- subprocess.check_call(argv)
+ try:
+ subprocess.check_call(argv)
+ except subprocess.CalledProcessError as e:
+ if e.returncode == -signal.SIGSEGV:
+ # pylint: disable=no-member
+ # https://github.com/The-Compiler/qutebrowser/issues/1387
+ pytest.xfail("Ignoring segfault on exit...")
+ else:
+ raise
def test_smoke_quteproc(quteproc):
diff --git a/tests/integration/testprocess.py b/tests/integration/testprocess.py
index 5c50121e5..1898a24ba 100644
--- a/tests/integration/testprocess.py
+++ b/tests/integration/testprocess.py
@@ -31,6 +31,8 @@ from PyQt5.QtTest import QSignalSpy
from helpers import utils
+from qutebrowser.utils import utils as quteutils
+
class InvalidLine(Exception):
@@ -76,7 +78,11 @@ class Line:
def _render_log(data, threshold=100):
"""Shorten the given log without -v and convert to a string."""
# pylint: disable=no-member
- if len(data) > threshold and not pytest.config.getoption('--verbose'):
+ is_exception = any('Traceback (most recent call last):' in line
+ for line in data)
+ if (len(data) > threshold and
+ not pytest.config.getoption('--verbose') and
+ not is_exception):
msg = '[{} lines suppressed, use -v to show]'.format(
len(data) - threshold)
data = [msg] + data[-threshold:]
@@ -151,6 +157,11 @@ class Process(QObject):
print(line)
self.captured_log.append(line)
+ def log_summary(self, text):
+ """Log the given line as summary/title."""
+ text = '\n{line} {text} {line}\n'.format(line='='*30, text=text)
+ self._log(text)
+
def _parse_line(self, line):
"""Parse the given line from the log.
@@ -276,7 +287,7 @@ class Process(QObject):
# Exit the process to make sure we're in a defined state again
self.terminate()
self.clear_data()
- raise InvalidLine(self._invalid)
+ raise InvalidLine
self.clear_data()
if not self.is_running() and not self.exit_expected:
@@ -327,6 +338,7 @@ class Process(QObject):
Return: either the found line or None.
"""
+ __tracebackhide__ = True
for line in self._data:
matches = []
@@ -342,6 +354,39 @@ class Process(QObject):
return line
return None
+ def _wait_for_new(self, timeout, do_skip, **kwargs):
+ """Wait for a log message which doesn't exist yet.
+
+ Called via wait_for.
+ """
+ __tracebackhide__ = True
+ message = kwargs.get('message', None)
+ if message is not None:
+ elided = quteutils.elide(repr(message), 50)
+ self._log("\n----> Waiting for {} in the log".format(elided))
+
+ spy = QSignalSpy(self.new_data)
+ elapsed_timer = QElapsedTimer()
+ elapsed_timer.start()
+
+ while True:
+ # Skip if there are pending messages causing a skip
+ self._maybe_skip()
+ got_signal = spy.wait(timeout)
+ if not got_signal or elapsed_timer.hasExpired(timeout):
+ msg = "Timed out after {}ms waiting for {!r}.".format(
+ timeout, kwargs)
+ if do_skip:
+ pytest.skip(msg)
+ else:
+ raise WaitForTimeout(msg)
+
+ match = self._wait_for_match(spy, kwargs)
+ if match is not None:
+ if message is not None:
+ self._log("----> found it")
+ return match
+
def _wait_for_match(self, spy, kwargs):
"""Try matching the kwargs with the given QSignalSpy."""
for args in spy:
@@ -406,31 +451,12 @@ class Process(QObject):
for key in kwargs:
assert key in self.KEYS
- # Search existing messages
existing = self._wait_for_existing(override_waited_for, **kwargs)
if existing is not None:
return existing
-
- # If there is none, wait for the message
- spy = QSignalSpy(self.new_data)
- elapsed_timer = QElapsedTimer()
- elapsed_timer.start()
-
- while True:
- # Skip if there are pending messages causing a skip
- self._maybe_skip()
- got_signal = spy.wait(timeout)
- if not got_signal or elapsed_timer.hasExpired(timeout):
- msg = "Timed out after {}ms waiting for {!r}.".format(
- timeout, kwargs)
- if do_skip:
- pytest.skip(msg)
- else:
- raise WaitForTimeout(msg)
-
- match = self._wait_for_match(spy, kwargs)
- if match is not None:
- return match
+ else:
+ return self._wait_for_new(timeout=timeout, do_skip=do_skip,
+ **kwargs)
def ensure_not_logged(self, delay=500, **kwargs):
"""Make sure the data matching the given arguments is not logged.