qutebrowser/tests/integration/features/conftest.py

386 lines
13 KiB
Python
Raw Normal View History

2015-11-02 23:49:44 +01:00
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015 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/>.
"""Steps for bdd-like tests."""
import os
import re
import sys
2015-11-21 14:23:44 +01:00
import time
2015-11-23 14:37:40 +01:00
import json
import os.path
2015-11-01 22:10:48 +01:00
import logging
import collections
2015-10-11 13:53:59 +02:00
2015-11-27 11:50:23 +01:00
import pytest
import yaml
2015-11-02 20:32:15 +01:00
import pytest_bdd as bdd
from PyQt5.QtGui import QClipboard
2015-10-11 13:53:59 +02:00
2015-11-02 23:49:44 +01:00
from helpers import utils # pylint: disable=import-error
2015-10-11 13:53:59 +02:00
def _clipboard_mode(qapp, what):
"""Get the QClipboard::Mode to use based on a string."""
if what == 'clipboard':
return QClipboard.Clipboard
elif what == 'primary selection':
assert qapp.clipboard().supportsSelection()
return QClipboard.Selection
else:
raise AssertionError
def pytest_collection_modifyitems(items):
"""Handle markers for xfail caret tests on OS X/Windows.
See https://github.com/The-Compiler/qutebrowser/issues/1142
We need to do this this way because we can't use markers with arguments
inside feature files.
"""
osx_xfail_marker = pytest.mark.xfail(
sys.platform == 'darwin',
reason='https://github.com/The-Compiler/qutebrowser/issues/1142')
windows_xfail_marker = pytest.mark.xfail(
os.name == 'nt',
reason='https://github.com/The-Compiler/qutebrowser/issues/1142')
for item in items:
if item.get_marker('xfail_issue1142_osx'):
item.add_marker(osx_xfail_marker)
if item.get_marker('xfail_issue1142_windows'):
item.add_marker(windows_xfail_marker)
2015-11-26 14:25:33 +01:00
## Given
2015-10-11 13:53:59 +02:00
@bdd.given(bdd.parsers.parse("I set {sect} -> {opt} to {value}"))
2015-11-26 14:25:33 +01:00
def set_setting_given(quteproc, httpbin, sect, opt, value):
"""Set a qutebrowser setting.
This is available as "Given:" step so it can be used as "Background:".
"""
value = value.replace('(port)', str(httpbin.port))
2015-10-11 13:53:59 +02:00
quteproc.set_setting(sect, opt, value)
@bdd.given(bdd.parsers.parse("I open {path}"))
def open_path_given(quteproc, path):
2015-11-26 14:25:33 +01:00
"""Open a URL.
This is available as "Given:" step so it can be used as "Background:".
It always opens a new tab, unlike "When I open ..."
"""
2015-10-13 07:14:32 +02:00
quteproc.open_path(path, new_tab=True)
2015-10-11 13:53:59 +02:00
2015-11-26 14:25:33 +01:00
@bdd.given(bdd.parsers.parse("I run {command}"))
def run_command_given(quteproc, command):
"""Run a qutebrowser command.
This is available as "Given:" step so it can be used as "Background:".
"""
quteproc.send_cmd(command)
@bdd.given("I have a fresh instance")
def fresh_instance(quteproc):
"""Restart qutebrowser instance for tests needing a fresh state."""
quteproc.terminate()
quteproc.start()
## When
2015-10-11 13:53:59 +02:00
@bdd.when(bdd.parsers.parse("I open {path}"))
2015-11-26 14:25:33 +01:00
def open_path(quteproc, path):
"""Open a URL.
If used like "When I open ... in a new tab", the URL is opened ina new
tab.
"""
new_tab_suffix = ' in a new tab'
if path.endswith(new_tab_suffix):
path = path[:-len(new_tab_suffix)]
new_tab = True
else:
new_tab = False
quteproc.open_path(path, new_tab=new_tab)
2015-10-11 13:53:59 +02:00
@bdd.when(bdd.parsers.parse("I set {sect} -> {opt} to {value}"))
2015-11-26 14:25:33 +01:00
def set_setting(quteproc, httpbin, sect, opt, value):
"""Set a qutebrowser setting."""
value = value.replace('(port)', str(httpbin.port))
quteproc.set_setting(sect, opt, value)
2015-10-11 13:53:59 +02:00
@bdd.when(bdd.parsers.parse("I run {command}"))
2015-11-26 14:25:33 +01:00
def run_command(quteproc, httpbin, command):
"""Run a qutebrowser command.
The suffix "with count ..." can be used to pass a count to the command.
"""
2015-11-15 20:48:07 +01:00
if 'with count' in command:
command, count = command.split(' with count ')
count = int(count)
else:
count = None
command = command.replace('(port)', str(httpbin.port))
2015-11-15 20:48:07 +01:00
quteproc.send_cmd(command, count=count)
2015-10-11 13:53:59 +02:00
@bdd.when(bdd.parsers.parse("I reload"))
2015-10-27 08:07:16 +01:00
def reload(qtbot, httpbin, quteproc, command):
2015-11-26 14:25:33 +01:00
"""Reload and wait until a new request is received."""
with qtbot.waitSignal(httpbin.new_request, raising=True):
quteproc.send_cmd(':reload')
@bdd.when(bdd.parsers.parse("I wait until {path} is loaded"))
def wait_until_loaded(quteproc, path):
2015-11-26 14:25:33 +01:00
"""Wait until the given path is loaded (as per qutebrowser log)."""
quteproc.wait_for_load_finished(path)
@bdd.when(bdd.parsers.re(r'I wait for (?P<is_regex>regex )?"'
r'(?P<pattern>[^"]+)" in the log'))
def wait_in_log(quteproc, is_regex, pattern):
2015-11-26 14:25:33 +01:00
"""Wait for a given pattern in the qutebrowser log.
If used like "When I wait for regex ... in the log" the argument is treated
as regex. Otherwise, it's treated as a pattern (* can be used as wildcard).
"""
if is_regex:
pattern = re.compile(pattern)
quteproc.wait_for(message=pattern)
@bdd.when(bdd.parsers.re(r'I wait for the (?P<category>error|message|warning) '
r'"(?P<message>.*)"'))
def wait_for_message(quteproc, httpbin, category, message):
2015-11-26 14:25:33 +01:00
"""Wait for a given statusbar message/error/warning."""
expect_message(quteproc, httpbin, category, message)
@bdd.when(bdd.parsers.parse("I wait {delay}s"))
def wait_time(quteproc, delay):
"""Sleep for the given delay."""
time.sleep(float(delay))
@bdd.when(bdd.parsers.re('I press the keys? "(?P<keys>[^"]*)"'))
def press_keys(quteproc, keys):
"""Send the given fake keys to qutebrowser."""
quteproc.press_keys(keys)
@bdd.when("selection is supported")
def selection_supported(qapp):
"""Skip the test if selection isn't supported."""
if not qapp.clipboard().supportsSelection():
pytest.skip("OS doesn't support primary selection!")
@bdd.when(bdd.parsers.re(r'I put "(?P<content>.*)" into the '
r'(?P<what>primary selection|clipboard)'))
def fill_clipboard(qtbot, qapp, httpbin, what, content):
mode = _clipboard_mode(qapp, what)
content = content.replace('(port)', str(httpbin.port))
clipboard = qapp.clipboard()
with qtbot.waitSignal(clipboard.changed):
clipboard.setText(content, mode)
2015-11-26 14:25:33 +01:00
## Then
2015-10-11 13:53:59 +02:00
@bdd.then(bdd.parsers.parse("{path} should be loaded"))
def path_should_be_loaded(httpbin, path):
2015-11-26 14:25:33 +01:00
"""Make sure the given path was loaded from the webserver."""
httpbin.wait_for(verb='GET', path='/' + path)
2015-10-11 13:53:59 +02:00
@bdd.then(bdd.parsers.parse("The requests should be:\n{pages}"))
2015-11-26 14:25:33 +01:00
def list_of_requests(httpbin, pages):
"""Make sure the given requests were done from the webserver."""
expected_requests = [httpbin.ExpectedRequest('GET', '/' + path.strip())
for path in pages.split('\n')]
actual_requests = httpbin.get_requests()
assert actual_requests == expected_requests
2015-10-22 06:44:05 +02:00
@bdd.then(bdd.parsers.parse("The unordered requests should be:\n{pages}"))
2015-11-26 14:25:33 +01:00
def list_of_requests_unordered(httpbin, pages):
"""Make sure the given requests were done (in no particular order)."""
expected_requests = [httpbin.ExpectedRequest('GET', '/' + path.strip())
for path in pages.split('\n')]
actual_requests = httpbin.get_requests()
# Requests are not hashable, we need to convert to ExpectedRequests
2015-11-26 14:25:33 +01:00
actual_requests = [httpbin.ExpectedRequest.from_request(req)
for req in actual_requests]
assert (collections.Counter(actual_requests) ==
collections.Counter(expected_requests))
@bdd.then(bdd.parsers.re(r'the (?P<category>error|message|warning) '
2015-11-26 17:50:39 +01:00
r'"(?P<message>.*)" should be shown'))
2015-11-26 14:25:33 +01:00
def expect_message(quteproc, httpbin, category, message):
"""Expect the given message in the qutebrowser log."""
2015-11-02 07:43:37 +01:00
category_to_loglevel = {
'message': logging.INFO,
'error': logging.ERROR,
'warning': logging.WARNING,
2015-11-02 07:43:37 +01:00
}
message = message.replace('(port)', str(httpbin.port))
quteproc.mark_expected(category='message',
loglevel=category_to_loglevel[category],
2015-11-01 22:10:48 +01:00
message=message)
2015-11-26 14:25:33 +01:00
@bdd.then(bdd.parsers.re(r'(?P<is_regex>regex )?"(?P<pattern>[^"]+)" should '
r'be logged'))
def should_be_logged(quteproc, is_regex, pattern):
"""Expect the given pattern on regex in the log."""
if is_regex:
pattern = re.compile(pattern)
quteproc.wait_for(message=pattern)
2015-11-25 17:19:16 +01:00
@bdd.then(bdd.parsers.parse('"{pattern}" should not be logged'))
def ensure_not_logged(quteproc, pattern):
2015-11-26 14:25:33 +01:00
"""Make sure the given pattern was *not* logged."""
quteproc.ensure_not_logged(message=pattern)
@bdd.then(bdd.parsers.parse('the javascript message "{message}" should be '
'logged'))
def javascript_message_logged(quteproc, message):
2015-11-26 14:25:33 +01:00
"""Make sure the given message was logged via javascript."""
quteproc.wait_for(category='js', function='javaScriptConsoleMessage',
message='[*] {}'.format(message))
@bdd.then(bdd.parsers.parse('the javascript message "{message}" should not be '
'logged'))
def javascript_message_not_logged(quteproc, message):
2015-11-26 14:25:33 +01:00
"""Make sure the given message was *not* logged via javascript."""
quteproc.ensure_not_logged(category='js',
function='javaScriptConsoleMessage',
message='[*] {}'.format(message))
2015-11-26 14:25:33 +01:00
@bdd.then(bdd.parsers.parse("The session should look like:\n{expected}"))
def compare_session(quteproc, expected):
"""Compare the current sessions against the given template.
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)
@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."""
pass
2015-11-23 14:37:40 +01:00
@bdd.then(bdd.parsers.parse("the header {header} should be set to {value}"))
def check_header(quteproc, header, value):
"""Check if a given header is set correctly.
This assumes we're on the httpbin header page.
"""
content = quteproc.get_content()
data = json.loads(content)
print(data)
assert data['headers'][header] == value
@bdd.then(bdd.parsers.parse("the page source should look like {filename}"))
def check_contents(quteproc, filename):
"""Check the current page's content.
The filename is interpreted relative to tests/integration/data.
"""
content = quteproc.get_content(plain=False)
path = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..',
'data', os.path.join(*filename.split('/')))
with open(path, 'r', encoding='utf-8') as f:
assert content == f.read()
@bdd.then(bdd.parsers.parse("the following tabs should be open:\n{tabs}"))
def check_open_tabs(quteproc, tabs):
2015-11-26 14:25:33 +01:00
"""Check the list of open tabs in the session.
This is a lightweight alternative for "The session should look like: ...".
It expects a list of URLs, with an optional "(active)" suffix.
"""
session = quteproc.get_session()
active_suffix = ' (active)'
tabs = tabs.splitlines()
assert len(session['windows']) == 1
assert len(session['windows'][0]['tabs']) == len(tabs)
for i, line in enumerate(tabs):
line = line.strip()
line = line[2:] # remove "- " prefix
if line.endswith(active_suffix):
path = line[:-len(active_suffix)]
active = True
else:
path = line
active = False
session_tab = session['windows'][0]['tabs'][i]
assert session_tab['history'][-1]['url'] == quteproc.path_to_url(path)
if active:
assert session_tab['active']
else:
assert 'active' not in session_tab
@bdd.then(bdd.parsers.re(r'the (?P<what>primary selection|clipboard) should '
r'contain "(?P<content>.*)"'))
def clipboard_contains(qapp, httpbin, what, content):
mode = _clipboard_mode(qapp, what)
expected = content.replace('(port)', str(httpbin.port))
expected = expected.replace('\\n', '\n')
data = qapp.clipboard().text(mode=mode)
assert data == expected