qutebrowser/tests/unit/browser/test_webelem.py

797 lines
30 KiB
Python
Raw Normal View History

2014-06-19 09:04:37 +02:00
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
2016-01-04 07:12:39 +01:00
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
2014-05-05 15:16:27 +02:00
#
# 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 the webelement utils."""
from unittest import mock
2014-09-04 08:00:05 +02:00
import collections.abc
2015-08-02 23:48:55 +02:00
import operator
2015-08-03 23:22:40 +02:00
import itertools
import binascii
import os.path
2014-05-05 15:16:27 +02:00
2015-08-03 23:22:40 +02:00
import hypothesis
import hypothesis.strategies
2015-08-10 19:47:19 +02:00
from PyQt5.QtCore import PYQT_VERSION, QRect, QPoint
2014-09-04 08:00:05 +02:00
from PyQt5.QtWebKit import QWebElement
2015-04-03 23:36:35 +02:00
import pytest
2014-05-05 15:16:27 +02:00
2014-09-08 10:30:05 +02:00
from qutebrowser.browser import webelem
2014-05-05 15:16:27 +02:00
def get_webelem(geometry=None, frame=None, null=False, style=None,
2014-09-04 08:00:05 +02:00
display='', attributes=None, tagname=None, classes=None):
"""Factory for WebElementWrapper objects based on a mock.
Args:
geometry: The geometry of the QWebElement as QRect.
frame: The QWebFrame the element is in.
null: Whether the element is null or not.
style: A dict with the styleAttributes of the element.
2014-09-04 08:00:05 +02:00
attributes: Boolean HTML attributes to be added.
tagname: The tag name.
classes: HTML classes to be added.
"""
elem = mock.Mock()
2014-09-04 08:00:05 +02:00
elem.isNull.return_value = null
elem.geometry.return_value = geometry
elem.webFrame.return_value = frame
elem.tagName.return_value = tagname
elem.toOuterXml.return_value = '<fakeelem/>'
2015-08-02 23:48:55 +02:00
elem.toPlainText.return_value = 'text'
2015-08-02 23:48:55 +02:00
attribute_dict = {}
if attributes is None:
pass
elif not isinstance(attributes, collections.abc.Mapping):
attribute_dict.update({e: None for e in attributes})
2014-09-04 08:00:05 +02:00
else:
2015-08-02 23:48:55 +02:00
attribute_dict.update(attributes)
elem.hasAttribute.side_effect = lambda k: k in attribute_dict
elem.attribute.side_effect = lambda k: attribute_dict.get(k, '')
elem.setAttribute.side_effect = (lambda k, v:
operator.setitem(attribute_dict, k, v))
2015-08-02 23:48:55 +02:00
elem.removeAttribute.side_effect = attribute_dict.pop
elem.attributeNames.return_value = list(attribute_dict)
2014-09-04 08:00:05 +02:00
if classes is not None:
elem.classes.return_value = classes.split(' ')
else:
elem.classes.return_value = []
style_dict = {'visibility': '', 'display': ''}
if style is not None:
style_dict.update(style)
2014-09-04 08:00:05 +02:00
def _style_property(name, strategy):
"""Helper function to act as styleProperty method."""
if strategy != QWebElement.ComputedStyle:
raise ValueError("styleProperty called with strategy != "
"ComputedStyle ({})!".format(strategy))
return style_dict[name]
2014-09-04 08:00:05 +02:00
elem.styleProperty.side_effect = _style_property
wrapped = webelem.WebElementWrapper(elem)
return wrapped
2015-08-03 23:22:40 +02:00
class SelectionAndFilterTests:
"""Generator for tests for TestSelectionsAndFilters."""
# A mapping of a HTML element to a list of groups where the selectors
# (after filtering) should match.
#
# Based on this, test cases are generated to make sure it matches those
# groups and not the others.
TESTS = [
('<foo />', []),
('<foo bar="baz"/>', []),
('<foo href="baz"/>', [webelem.Group.url]),
('<foo src="baz"/>', [webelem.Group.url]),
('<a />', [webelem.Group.all]),
('<a href="foo" />', [webelem.Group.all, webelem.Group.links,
webelem.Group.prevnext, webelem.Group.url]),
('<a href="javascript://foo" />', [webelem.Group.all,
webelem.Group.url]),
2015-08-03 23:22:40 +02:00
('<area />', [webelem.Group.all]),
('<area href="foo" />', [webelem.Group.all, webelem.Group.links,
webelem.Group.prevnext, webelem.Group.url]),
('<area href="javascript://foo" />', [webelem.Group.all,
webelem.Group.url]),
('<link />', [webelem.Group.all]),
('<link href="foo" />', [webelem.Group.all, webelem.Group.links,
webelem.Group.prevnext, webelem.Group.url]),
('<link href="javascript://foo" />', [webelem.Group.all,
webelem.Group.url]),
('<textarea />', [webelem.Group.all]),
('<select />', [webelem.Group.all]),
('<input />', [webelem.Group.all]),
('<input type="hidden" />', []),
('<button />', [webelem.Group.all]),
('<button href="foo" />', [webelem.Group.all, webelem.Group.prevnext,
webelem.Group.url]),
('<button href="javascript://foo" />', [webelem.Group.all,
webelem.Group.url]),
# We can't easily test <frame>/<iframe> as they vanish when setting
# them via QWebFrame::setHtml...
('<p onclick="foo" foo="bar"/>', [webelem.Group.all]),
('<p onmousedown="foo" foo="bar"/>', [webelem.Group.all]),
('<p role="option" foo="bar"/>', [webelem.Group.all]),
('<p role="button" foo="bar"/>', [webelem.Group.all]),
('<p role="button" href="bar"/>', [webelem.Group.all,
webelem.Group.prevnext,
webelem.Group.url]),
]
GROUPS = [e for e in webelem.Group if e != webelem.Group.focus]
COMBINATIONS = list(itertools.product(TESTS, GROUPS))
def __init__(self):
self.tests = list(self._generate_tests())
def _generate_tests(self):
for (val, matching_groups), group in self.COMBINATIONS:
if group in matching_groups:
yield group, val, True
else:
yield group, val, False
class TestSelectorsAndFilters:
TESTS = SelectionAndFilterTests().tests
def test_test_generator(self):
assert self.TESTS
@pytest.mark.parametrize('group, val, matching', TESTS)
def test_selectors(self, webframe, group, val, matching):
webframe.setHtml('<html><body>{}</body></html>'.format(val))
# Make sure setting HTML succeeded and there's a new element
assert len(webframe.findAllElements('*')) == 3
elems = webframe.findAllElements(webelem.SELECTORS[group])
elems = [webelem.WebElementWrapper(e) for e in elems]
filterfunc = webelem.FILTERS.get(group, lambda e: True)
elems = [e for e in elems if filterfunc(e)]
assert bool(elems) == matching
class TestWebElementWrapper:
2014-09-04 08:00:05 +02:00
2015-08-03 23:22:40 +02:00
"""Generic tests for WebElementWrapper.
Note: For some methods, there's a dedicated test class with more involved
tests.
"""
2014-09-04 08:00:05 +02:00
2015-08-02 23:48:55 +02:00
@pytest.fixture
def elem(self):
return get_webelem()
2014-09-04 08:00:05 +02:00
def test_nullelem(self):
"""Test __init__ with a null element."""
2015-04-03 23:36:35 +02:00
with pytest.raises(webelem.IsNullError):
2014-09-04 08:00:05 +02:00
get_webelem(null=True)
2015-08-02 23:48:55 +02:00
def test_double_wrap(self, elem):
"""Test wrapping a WebElementWrapper."""
with pytest.raises(TypeError) as excinfo:
webelem.WebElementWrapper(elem)
assert str(excinfo.value) == "Trying to wrap a wrapper!"
@pytest.mark.parametrize('code', [
str,
lambda e: e[None],
lambda e: operator.setitem(e, None, None),
lambda e: operator.delitem(e, None),
lambda e: None in e,
len,
lambda e: e.is_visible(None),
lambda e: e.rect_on_view(),
lambda e: e.is_writable(),
lambda e: e.is_content_editable(),
lambda e: e.is_editable(),
lambda e: e.is_text_input(),
lambda e: e.debug_text(),
list, # __iter__
])
def test_vanished(self, elem, code):
"""Make sure methods check if the element is vanished."""
elem._elem.isNull.return_value = True
with pytest.raises(webelem.IsNullError):
code(elem)
def test_str(self, elem):
assert str(elem) == 'text'
@pytest.mark.parametrize('is_null, expected', [
(False, "<qutebrowser.browser.webelem.WebElementWrapper "
"html='<fakeelem/>'>"),
(True, '<qutebrowser.browser.webelem.WebElementWrapper html=None>'),
])
def test_repr(self, elem, is_null, expected):
elem._elem.isNull.return_value = is_null
assert repr(elem) == expected
def test_getitem(self):
elem = get_webelem(attributes={'foo': 'bar'})
assert elem['foo'] == 'bar'
def test_getitem_keyerror(self, elem):
with pytest.raises(KeyError):
elem['foo'] # pylint: disable=pointless-statement
def test_setitem(self, elem):
elem['foo'] = 'bar'
assert elem._elem.attribute('foo') == 'bar'
def test_delitem(self):
elem = get_webelem(attributes={'foo': 'bar'})
del elem['foo']
assert not elem._elem.hasAttribute('foo')
def test_setitem_keyerror(self, elem):
with pytest.raises(KeyError):
del elem['foo']
def test_contains(self):
elem = get_webelem(attributes={'foo': 'bar'})
assert 'foo' in elem
assert 'bar' not in elem
@pytest.mark.parametrize('attributes, expected', [
({'one': '1', 'two': '2'}, {'one', 'two'}),
({}, set()),
])
def test_iter(self, attributes, expected):
elem = get_webelem(attributes=attributes)
assert set(elem) == expected
@pytest.mark.parametrize('attributes, length', [
({'one': '1', 'two': '2'}, 2),
({}, 0),
])
def test_len(self, attributes, length):
elem = get_webelem(attributes=attributes)
assert len(elem) == length
2014-09-04 08:00:05 +02:00
2015-08-03 23:22:40 +02:00
@pytest.mark.parametrize('attributes, writable', [
([], True),
(['disabled'], False),
(['readonly'], False),
(['disabled', 'readonly'], False),
])
def test_is_writable(self, attributes, writable):
elem = get_webelem(attributes=attributes)
assert elem.is_writable() == writable
@pytest.mark.parametrize('attributes, expected', [
({}, False),
({'contenteditable': 'false'}, False),
({'contenteditable': 'inherit'}, False),
({'contenteditable': 'true'}, True),
])
def test_is_content_editable(self, attributes, expected):
elem = get_webelem(attributes=attributes)
assert elem.is_content_editable() == expected
@pytest.mark.parametrize('tagname, attributes, expected', [
('input', {}, True),
('textarea', {}, True),
('select', {}, False),
('foo', {'role': 'combobox'}, True),
('foo', {'role': 'textbox'}, True),
('foo', {'role': 'bar'}, False),
('input', {'role': 'bar'}, True),
])
def test_is_text_input(self, tagname, attributes, expected):
elem = get_webelem(tagname=tagname, attributes=attributes)
assert elem.is_text_input() == expected
@pytest.mark.parametrize('xml, expected', [
('<fakeelem/>', '<fakeelem/>'),
('<foo>\n<bar/>\n</foo>', '<foo><bar/></foo>'),
('<foo>{}</foo>'.format('x' * 500), '<foo>{}'.format('x' * 494)),
2015-09-16 20:25:02 +02:00
], ids=['fakeelem', 'newlines', 'long'])
2015-08-03 23:22:40 +02:00
def test_debug_text(self, elem, xml, expected):
elem._elem.toOuterXml.return_value = xml
assert elem.debug_text() == expected
class TestIsVisible:
2014-05-12 11:41:35 +02:00
@pytest.fixture
def frame(self, stubs):
return stubs.FakeWebFrame(QRect(0, 0, 100, 100))
2014-05-05 15:16:27 +02:00
2015-08-10 19:37:16 +02:00
def test_invalid_frame_geometry(self, stubs):
"""Test with an invalid frame geometry."""
rect = QRect(0, 0, 0, 0)
assert not rect.isValid()
frame = stubs.FakeWebFrame(rect)
elem = get_webelem(QRect(0, 0, 10, 10), frame)
assert not elem.is_visible(frame)
def test_invalid_invisible(self, frame):
2014-05-12 11:41:35 +02:00
"""Test elements with an invalid geometry which are invisible."""
elem = get_webelem(QRect(0, 0, 0, 0), frame)
2015-04-03 23:36:35 +02:00
assert not elem.geometry().isValid()
assert elem.geometry().x() == 0
assert not elem.is_visible(frame)
2014-05-05 15:16:27 +02:00
def test_invalid_visible(self, frame):
2014-05-12 11:41:35 +02:00
"""Test elements with an invalid geometry which are visible.
This seems to happen sometimes in the real world, with real elements
which *are* visible, but don't have a valid geometry.
"""
elem = get_webelem(QRect(10, 10, 0, 0), frame)
2015-04-03 23:36:35 +02:00
assert not elem.geometry().isValid()
assert elem.is_visible(frame)
@pytest.mark.parametrize('geometry, visible', [
(QRect(5, 5, 4, 4), False),
(QRect(10, 10, 1, 1), True),
])
def test_scrolled(self, geometry, visible, stubs):
scrolled_frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100),
scroll=QPoint(10, 10))
elem = get_webelem(geometry, scrolled_frame)
assert elem.is_visible(scrolled_frame) == visible
@pytest.mark.parametrize('style, visible', [
({'visibility': 'visible'}, True),
({'visibility': 'hidden'}, False),
({'display': 'inline'}, True),
({'display': 'none'}, False),
({'visibility': 'visible', 'display': 'none'}, False),
({'visibility': 'hidden', 'display': 'inline'}, False),
])
def test_css_attributes(self, frame, style, visible):
elem = get_webelem(QRect(0, 0, 10, 10), frame, style=style)
assert elem.is_visible(frame) == visible
class TestIsVisibleIframe:
2014-05-12 12:16:41 +02:00
2014-05-27 13:06:13 +02:00
"""Tests for is_visible with a child frame.
Attributes:
frame: The FakeWebFrame we're using to test.
iframe: The iframe inside frame.
elem1-elem4: FakeWebElements to test.
"""
2014-05-12 12:16:41 +02:00
Objects = collections.namedtuple('Objects', ['frame', 'iframe', 'elems'])
@pytest.fixture
def objects(self, stubs):
2015-03-26 07:08:13 +01:00
"""Set up the following base situation.
0, 0 300, 0
##############################
# #
0,10 # iframe 100,10 #
#********** #
#*e * elems[0]: 0, 0 in iframe (visible)
2015-03-26 07:08:13 +01:00
#* * #
#* e * elems[1]: 20,90 in iframe (visible)
2015-03-26 07:08:13 +01:00
#********** #
0,110 #. .100,110 #
#. . #
#. e . elems[2]: 20,150 in iframe (not visible)
2015-03-26 07:08:13 +01:00
#.......... #
# e elems[3]: 30, 180 in main frame (visible)
2015-03-26 07:08:13 +01:00
# #
# frame #
##############################
300, 0 300, 300
Returns an Objects namedtuple with frame/iframe/elems attributes.
2014-05-12 12:16:41 +02:00
"""
frame = stubs.FakeWebFrame(QRect(0, 0, 300, 300))
iframe = stubs.FakeWebFrame(QRect(0, 10, 100, 100), parent=frame)
assert frame.geometry().contains(iframe.geometry())
elems = [
get_webelem(QRect(0, 0, 10, 10), iframe),
get_webelem(QRect(20, 90, 10, 10), iframe),
get_webelem(QRect(20, 150, 10, 10), iframe),
get_webelem(QRect(30, 180, 10, 10), frame),
]
assert elems[0].is_visible(frame)
assert elems[1].is_visible(frame)
assert not elems[2].is_visible(frame)
assert elems[3].is_visible(frame)
return self.Objects(frame=frame, iframe=iframe, elems=elems)
def test_iframe_scrolled(self, objects):
2014-05-12 12:16:41 +02:00
"""Scroll iframe down so elem3 gets visible and elem1/elem2 not."""
objects.iframe.scrollPosition.return_value = QPoint(0, 100)
assert not objects.elems[0].is_visible(objects.frame)
assert not objects.elems[1].is_visible(objects.frame)
assert objects.elems[2].is_visible(objects.frame)
assert objects.elems[3].is_visible(objects.frame)
2014-05-12 12:16:41 +02:00
def test_mainframe_scrolled_iframe_visible(self, objects):
2014-05-12 12:16:41 +02:00
"""Scroll mainframe down so iframe is partly visible but elem1 not."""
objects.frame.scrollPosition.return_value = QPoint(0, 50)
geom = objects.frame.geometry().translated(
objects.frame.scrollPosition())
assert not geom.contains(objects.iframe.geometry())
assert geom.intersects(objects.iframe.geometry())
assert not objects.elems[0].is_visible(objects.frame)
assert objects.elems[1].is_visible(objects.frame)
assert not objects.elems[2].is_visible(objects.frame)
assert objects.elems[3].is_visible(objects.frame)
def test_mainframe_scrolled_iframe_invisible(self, objects):
2014-05-12 12:16:41 +02:00
"""Scroll mainframe down so iframe is invisible."""
objects.frame.scrollPosition.return_value = QPoint(0, 110)
geom = objects.frame.geometry().translated(
objects.frame.scrollPosition())
assert not geom.contains(objects.iframe.geometry())
assert not geom.intersects(objects.iframe.geometry())
assert not objects.elems[0].is_visible(objects.frame)
assert not objects.elems[1].is_visible(objects.frame)
assert not objects.elems[2].is_visible(objects.frame)
assert objects.elems[3].is_visible(objects.frame)
2015-08-10 19:37:16 +02:00
@pytest.fixture
def invalid_objects(self, stubs):
"""Set up the following base situation:
0, 0 300, 0
##############################
# #
0,10 # iframe 100,10 #
#********** #
#* e * elems[0]: 10, 10 in iframe (visible)
#* * #
#* * #
#********** #
0,110 #. .100,110 #
#. . #
#. e . elems[2]: 20,150 in iframe (not visible)
#.......... #
##############################
300, 0 300, 300
Returns an Objects namedtuple with frame/iframe/elems attributes.
"""
frame = stubs.FakeWebFrame(QRect(0, 0, 300, 300))
iframe = stubs.FakeWebFrame(QRect(0, 10, 100, 100), parent=frame)
assert frame.geometry().contains(iframe.geometry())
elems = [
get_webelem(QRect(10, 10, 0, 0), iframe),
get_webelem(QRect(20, 150, 0, 0), iframe),
]
for e in elems:
assert not e.geometry().isValid()
return self.Objects(frame=frame, iframe=iframe, elems=elems)
def test_invalid_visible(self, invalid_objects):
"""Test elements with an invalid geometry which are visible.
This seems to happen sometimes in the real world, with real elements
which *are* visible, but don't have a valid geometry.
"""
elem = invalid_objects.elems[0]
assert elem.is_visible(invalid_objects.frame)
def test_invalid_invisible(self, invalid_objects):
"""Test elements with an invalid geometry which are invisible."""
assert not invalid_objects.elems[1].is_visible(invalid_objects.frame)
def test_focus_element(stubs):
"""Test getting focus element with a fake frame/element.
Testing this with a real webpage is almost impossible because the window
and the element would have focus, which is hard to achieve consistently in
a test.
"""
frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100))
elem = get_webelem()
frame.focus_elem = elem._elem
assert webelem.focus_elem(frame)._elem is elem._elem
2015-08-03 23:22:40 +02:00
class TestRectOnView:
2015-08-03 23:22:40 +02:00
def test_simple(self, stubs):
geometry = QRect(5, 5, 4, 4)
frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100))
elem = get_webelem(geometry, frame)
assert elem.rect_on_view() == QRect(5, 5, 4, 4)
2015-08-03 23:22:40 +02:00
def test_scrolled(self, stubs):
geometry = QRect(20, 20, 4, 4)
frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100),
scroll=QPoint(10, 10))
elem = get_webelem(geometry, frame)
assert elem.rect_on_view() == QRect(20 - 10, 20 - 10, 4, 4)
def test_iframe(self, stubs):
"""Test an element in an iframe.
0, 0 200, 0
##############################
# #
0,10 # iframe 100,10 #
#********** #
#* * #
#* * #
#* e * elem: 20,90 in iframe
#********** #
0,100 # #
##############################
200, 0 200, 200
"""
frame = stubs.FakeWebFrame(QRect(0, 0, 200, 200))
iframe = stubs.FakeWebFrame(QRect(0, 10, 100, 100), parent=frame)
assert frame.geometry().contains(iframe.geometry())
elem = get_webelem(QRect(20, 90, 10, 10), iframe)
assert elem.rect_on_view() == QRect(20, 10 + 90, 10, 10)
def test_passed_geometry(self, stubs):
"""Make sure geometry isn't called when a geometry is passed."""
raw_elem = get_webelem()._elem
rect = QRect(10, 20, 30, 40)
assert webelem.rect_on_view(raw_elem, rect) == rect
assert not raw_elem.geometry.called
class TestJavascriptEscape:
TESTS = {
'foo\\bar': r'foo\\bar',
'foo\nbar': r'foo\nbar',
'foo\rbar': r'foo\rbar',
"foo'bar": r"foo\'bar",
'foo"bar': r'foo\"bar',
'one\\two\rthree\nfour\'five"six': r'one\\two\rthree\nfour\'five\"six',
'\x00': r'\x00',
'hellö': 'hellö',
'': '',
'\x80Ā': '\x80Ā',
'𐀀\x00𐀀\x00': r'𐀀\x00𐀀\x00',
'𐀀\ufeff': r'𐀀\ufeff',
'\ufeff': r'\ufeff',
# http://stackoverflow.com/questions/2965293/
'\u2028': r'\u2028',
'\u2029': r'\u2029',
2015-08-03 23:22:40 +02:00
}
2015-08-06 23:19:05 +02:00
# Once there was this warning here:
# load glyph failed err=6 face=0x2680ba0, glyph=1912
# http://qutebrowser.org:8010/builders/debian-jessie/builds/765/steps/unittests/
# Should that be ignored?
2015-09-16 20:25:02 +02:00
@pytest.mark.parametrize('before, after', TESTS.items(), ids=repr)
2015-08-03 23:22:40 +02:00
def test_fake_escape(self, before, after):
"""Test javascript escaping with some expected outcomes."""
assert webelem.javascript_escape(before) == after
def _test_escape(self, text, qtbot, webframe):
2015-08-03 23:22:40 +02:00
"""Helper function for test_real_escape*."""
try:
self._test_escape_simple(text, webframe)
except AssertionError:
# Try another method if the simple method failed.
#
# See _test_escape_hexlified documentation on why this is
# necessary.
self._test_escape_hexlified(text, qtbot, webframe)
def _test_escape_hexlified(self, text, qtbot, webframe):
"""Test conversion by hexlifying in javascript.
Since the conversion of QStrings to Python strings is broken in some
older PyQt versions in some corner cases, we load a HTML file which
generates an MD5 of the escaped text and use that for comparisons.
"""
escaped = webelem.javascript_escape(text)
path = os.path.join(os.path.dirname(__file__),
'test_webelem_jsescape.html')
with open(path, encoding='utf-8') as f:
html_source = f.read().replace('%INPUT%', escaped)
with qtbot.waitSignal(webframe.loadFinished) as blocker:
webframe.setHtml(html_source)
assert blocker.args == [True]
result = webframe.evaluateJavaScript('window.qute_test_result')
assert result is not None
assert '|' in result
result_md5, result_text = result.split('|', maxsplit=1)
text_md5 = binascii.hexlify(text.encode('utf-8')).decode('ascii')
assert result_md5 == text_md5, result_text
def _test_escape_simple(self, text, webframe):
"""Test conversion by using evaluateJavaScript."""
2015-08-03 23:22:40 +02:00
escaped = webelem.javascript_escape(text)
result = webframe.evaluateJavaScript('"{}";'.format(escaped))
assert result == text
2015-08-03 23:22:40 +02:00
2015-09-16 20:25:02 +02:00
@pytest.mark.parametrize('text', TESTS, ids=repr)
def test_real_escape(self, webframe, qtbot, text):
2015-08-03 23:22:40 +02:00
"""Test javascript escaping with a real QWebPage."""
self._test_escape(text, qtbot, webframe)
2015-08-03 23:22:40 +02:00
@pytest.mark.qt_log_ignore('^load glyph failed',
'^OpenType support missing for script',
extend=True)
@hypothesis.given(hypothesis.strategies.text())
def test_real_escape_hypothesis(self, webframe, qtbot, text):
2015-08-03 23:22:40 +02:00
"""Test javascript escaping with a real QWebPage and hypothesis."""
# We can't simply use self._test_escape because of this:
# https://github.com/pytest-dev/pytest-qt/issues/69
# self._test_escape(text, qtbot, webframe)
try:
self._test_escape_simple(text, webframe)
except AssertionError:
if PYQT_VERSION >= 0x050300:
self._test_escape_hexlified(text, qtbot, webframe)
2014-05-05 15:16:27 +02:00
class TestGetChildFrames:
2014-05-12 13:26:11 +02:00
"""Check get_child_frames."""
2015-04-03 23:36:35 +02:00
def test_single_frame(self, stubs):
2014-05-12 13:26:11 +02:00
"""Test get_child_frames with a single frame without children."""
2014-08-26 19:10:14 +02:00
frame = stubs.FakeChildrenFrame()
2014-05-12 13:26:11 +02:00
children = webelem.get_child_frames(frame)
2015-04-03 23:36:35 +02:00
assert len(children) == 1
assert children[0] is frame
2014-05-12 13:26:11 +02:00
frame.childFrames.assert_called_once_with()
2015-04-03 23:36:35 +02:00
def test_one_level(self, stubs):
2015-03-26 07:08:13 +01:00
r"""Test get_child_frames with one level of children.
2014-05-12 13:26:11 +02:00
o parent
/ \
child1 o o child2
"""
2014-08-26 19:10:14 +02:00
child1 = stubs.FakeChildrenFrame()
child2 = stubs.FakeChildrenFrame()
parent = stubs.FakeChildrenFrame([child1, child2])
2014-05-12 13:26:11 +02:00
children = webelem.get_child_frames(parent)
2015-04-03 23:36:35 +02:00
assert len(children) == 3
assert children[0] is parent
assert children[1] is child1
assert children[2] is child2
2014-05-12 13:26:11 +02:00
parent.childFrames.assert_called_once_with()
child1.childFrames.assert_called_once_with()
child2.childFrames.assert_called_once_with()
2015-04-03 23:36:35 +02:00
def test_multiple_levels(self, stubs):
2015-03-26 07:08:13 +01:00
r"""Test get_child_frames with multiple levels of children.
2014-05-12 13:26:11 +02:00
o root
/ \
o o first
/\ /\
o o o o second
"""
2014-08-26 19:10:14 +02:00
second = [stubs.FakeChildrenFrame() for _ in range(4)]
first = [stubs.FakeChildrenFrame(second[0:2]),
stubs.FakeChildrenFrame(second[2:4])]
root = stubs.FakeChildrenFrame(first)
2014-05-12 13:26:11 +02:00
children = webelem.get_child_frames(root)
2015-04-03 23:36:35 +02:00
assert len(children) == 7
assert children[0] is root
2014-05-12 13:26:11 +02:00
for frame in [root] + first + second:
2015-04-03 23:36:35 +02:00
frame.childFrames.assert_called_once_with()
2014-05-12 13:26:11 +02:00
class TestIsEditable:
2014-06-23 20:31:47 +02:00
"""Tests for is_editable."""
@pytest.fixture
def stubbed_config(self, config_stub, monkeypatch):
2015-04-05 20:30:31 +02:00
"""Fixture to create a config stub with an input section."""
config_stub.data = {'input': {}}
monkeypatch.setattr('qutebrowser.browser.webelem.config', config_stub)
return config_stub
2014-06-23 20:31:47 +02:00
@pytest.mark.parametrize('tagname, attributes, editable', [
('input', {}, True),
('input', {'type': 'text'}, True),
('INPUT', {'TYPE': 'TEXT'}, True), # caps attributes/name
('input', {'type': 'email'}, True),
('input', {'type': 'url'}, True),
('input', {'type': 'tel'}, True),
('input', {'type': 'number'}, True),
('input', {'type': 'password'}, True),
('input', {'type': 'search'}, True),
('textarea', {}, True),
('input', {'type': 'button'}, False),
('input', {'type': 'checkbox'}, False),
('select', {}, False),
('input', {'disabled': None}, False),
('input', {'readonly': None}, False),
('textarea', {'disabled': None}, False),
('textarea', {'readonly': None}, False),
2015-08-03 23:22:40 +02:00
('foobar', {}, False),
('foobar', {'contenteditable': 'true'}, True),
('foobar', {'contenteditable': 'false'}, False),
('foobar', {'contenteditable': 'true', 'disabled': None}, False),
('foobar', {'contenteditable': 'true', 'readonly': None}, False),
('foobar', {'role': 'foobar'}, False),
('foobar', {'role': 'combobox'}, True),
('foobar', {'role': 'textbox'}, True),
('foobar', {'role': 'combobox', 'disabled': None}, False),
('foobar', {'role': 'combobox', 'readonly': None}, False),
])
def test_is_editable(self, tagname, attributes, editable):
elem = get_webelem(tagname=tagname, attributes=attributes)
assert elem.is_editable() == editable
@pytest.mark.parametrize('classes, editable', [
(None, False),
('foo-kix-bar', False),
('foo kix-foo', True),
('KIX-FOO', False),
('foo CodeMirror-foo', True),
])
2015-08-02 23:48:55 +02:00
def test_is_editable_div(self, classes, editable):
elem = get_webelem(tagname='div', classes=classes)
assert elem.is_editable() == editable
@pytest.mark.parametrize('setting, tagname, attributes, editable', [
(True, 'embed', {}, True),
(True, 'embed', {}, True),
(False, 'applet', {}, False),
(False, 'applet', {}, False),
(True, 'object', {'type': 'application/foo'}, True),
(False, 'object', {'type': 'application/foo'}, False),
(True, 'object', {'type': 'foo', 'classid': 'foo'}, True),
(False, 'object', {'type': 'foo', 'classid': 'foo'}, False),
2015-08-03 23:22:40 +02:00
(True, 'object', {}, False),
(True, 'object', {'type': 'image/gif'}, False),
])
2015-08-02 23:48:55 +02:00
def test_is_editable_plugin(self, stubbed_config, setting, tagname,
attributes, editable):
stubbed_config.data['input']['insert-mode-on-plugins'] = setting
elem = get_webelem(tagname=tagname, attributes=attributes)
assert elem.is_editable() == editable