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."""
|
|
|
|
|
2015-03-01 22:10:16 +01:00
|
|
|
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
|
2015-08-04 08:49:42 +02:00
|
|
|
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
|
|
|
|
2016-06-13 10:49:58 +02:00
|
|
|
from qutebrowser.browser.webkit import webelem
|
2014-05-05 15:16:27 +02:00
|
|
|
|
|
|
|
|
2016-06-06 15:19:27 +02:00
|
|
|
def get_webelem(geometry=None, frame=None, *, null=False, style=None,
|
|
|
|
attributes=None, tagname=None, classes=None,
|
|
|
|
parent=None, js_rect_return=None, zoom_text_only=False):
|
2014-09-04 08:00:05 +02:00
|
|
|
"""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.
|
2015-08-02 22:10:00 +02:00
|
|
|
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.
|
2016-06-06 15:19:27 +02:00
|
|
|
js_rect_return: If None, what evaluateJavaScript returns is based on
|
|
|
|
geometry. If set, the return value of
|
|
|
|
evaluateJavaScript.
|
|
|
|
zoom_text_only: Whether zoom-text-only is set in the config
|
2014-09-04 08:00:05 +02:00
|
|
|
"""
|
2016-06-06 15:19:27 +02:00
|
|
|
# pylint: disable=too-many-locals,too-many-branches
|
2015-03-01 22:10:16 +01:00
|
|
|
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'
|
2016-03-30 19:08:37 +02:00
|
|
|
elem.parent.return_value = parent
|
2015-08-02 22:10:00 +02:00
|
|
|
|
2016-06-06 11:56:15 +02:00
|
|
|
if geometry is not None:
|
|
|
|
if frame is None:
|
|
|
|
scroll_x = 0
|
|
|
|
scroll_y = 0
|
|
|
|
else:
|
|
|
|
scroll_x = frame.scrollPosition().x()
|
|
|
|
scroll_y = frame.scrollPosition().y()
|
2016-06-06 15:19:27 +02:00
|
|
|
if js_rect_return is None:
|
|
|
|
if frame is None or zoom_text_only:
|
|
|
|
zoom = 1.0
|
|
|
|
else:
|
|
|
|
zoom = frame.zoomFactor()
|
|
|
|
elem.evaluateJavaScript.return_value = {
|
|
|
|
"length": 1,
|
|
|
|
"0": {
|
|
|
|
"left": (geometry.left() - scroll_x) / zoom,
|
|
|
|
"top": (geometry.top() - scroll_y) / zoom,
|
|
|
|
"right": (geometry.right() - scroll_x) / zoom,
|
|
|
|
"bottom": (geometry.bottom() - scroll_y) / zoom,
|
|
|
|
"width": geometry.width() / zoom,
|
|
|
|
"height": geometry.height() / zoom,
|
|
|
|
}
|
2016-06-06 11:56:15 +02:00
|
|
|
}
|
2016-06-06 15:19:27 +02:00
|
|
|
else:
|
|
|
|
elem.evaluateJavaScript.return_value = js_rect_return
|
2016-06-06 11:56:15 +02:00
|
|
|
|
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:
|
2016-01-21 18:07:56 +01:00
|
|
|
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)
|
2015-08-02 22:10:00 +02:00
|
|
|
|
2014-09-04 08:00:05 +02:00
|
|
|
if classes is not None:
|
|
|
|
elem.classes.return_value = classes.split(' ')
|
|
|
|
else:
|
|
|
|
elem.classes.return_value = []
|
|
|
|
|
2016-07-28 14:25:53 +02:00
|
|
|
style_dict = {'visibility': '', 'display': '', 'foo': 'bar'}
|
2015-08-02 22:10:00 +02:00
|
|
|
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))
|
2015-08-02 22:10:00 +02:00
|
|
|
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."""
|
|
|
|
|
2016-07-05 08:34:03 +02:00
|
|
|
# A mapping of an HTML element to a list of groups where the selectors
|
2015-08-03 23:22:40 +02:00
|
|
|
# (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,
|
2016-01-21 18:07:56 +01:00
|
|
|
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]),
|
|
|
|
|
2016-05-18 01:58:48 +02:00
|
|
|
('<textarea />', [webelem.Group.all, webelem.Group.inputs]),
|
2015-08-03 23:22:40 +02:00
|
|
|
('<select />', [webelem.Group.all]),
|
|
|
|
|
|
|
|
('<input />', [webelem.Group.all]),
|
|
|
|
('<input type="hidden" />', []),
|
2016-05-18 05:57:36 +02:00
|
|
|
('<input type="text" />', [webelem.Group.inputs, webelem.Group.all]),
|
|
|
|
('<input type="email" />', [webelem.Group.inputs, webelem.Group.all]),
|
|
|
|
('<input type="url" />', [webelem.Group.inputs, webelem.Group.all]),
|
|
|
|
('<input type="tel" />', [webelem.Group.inputs, webelem.Group.all]),
|
|
|
|
('<input type="number" />', [webelem.Group.inputs, webelem.Group.all]),
|
|
|
|
('<input type="password" />', [webelem.Group.inputs,
|
|
|
|
webelem.Group.all]),
|
|
|
|
('<input type="search" />', [webelem.Group.inputs, webelem.Group.all]),
|
2015-08-03 23:22:40 +02:00
|
|
|
|
|
|
|
('<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
|
|
|
|
|
|
|
|
|
2015-04-04 17:05:44 +02:00
|
|
|
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,
|
2016-07-28 13:12:47 +02:00
|
|
|
list, # __iter__
|
2015-08-02 23:48:55 +02:00
|
|
|
len,
|
2016-07-28 13:12:47 +02:00
|
|
|
lambda e: e.frame(),
|
|
|
|
lambda e: e.geometry(),
|
|
|
|
lambda e: e.document_element(),
|
|
|
|
lambda e: e.create_inside('span'),
|
|
|
|
lambda e: e.find_first('span'),
|
2016-07-28 14:25:53 +02:00
|
|
|
lambda e: e.style_property('visibility', QWebElement.ComputedStyle),
|
2016-07-28 13:12:47 +02:00
|
|
|
lambda e: e.text(),
|
|
|
|
lambda e: e.set_text('foo'),
|
|
|
|
lambda e: e.set_inner_xml(''),
|
|
|
|
lambda e: e.remove_from_document(),
|
|
|
|
lambda e: e.set_style_property('visibility', 'hidden'),
|
2015-08-02 23:48:55 +02:00
|
|
|
lambda e: e.is_writable(),
|
|
|
|
lambda e: e.is_content_editable(),
|
|
|
|
lambda e: e.is_editable(),
|
|
|
|
lambda e: e.is_text_input(),
|
2016-07-28 13:12:47 +02:00
|
|
|
lambda e: e.remove_blank_target(),
|
2015-08-02 23:48:55 +02:00
|
|
|
lambda e: e.debug_text(),
|
2016-07-28 13:12:47 +02:00
|
|
|
lambda e: e.outer_xml(),
|
|
|
|
lambda e: e.tag_name(),
|
|
|
|
lambda e: e.run_js_async(''),
|
|
|
|
lambda e: e.rect_on_view(),
|
|
|
|
lambda e: e.is_visible(None),
|
|
|
|
], ids=['str', 'getitem', 'setitem', 'delitem', 'contains', 'iter', 'len',
|
|
|
|
'frame', 'geometry', 'document_element', 'create_inside',
|
|
|
|
'find_first', 'style_property', 'text', 'set_text',
|
|
|
|
'set_inner_xml', 'remove_from_document', 'set_style_property',
|
|
|
|
'is_writable', 'is_content_editable', 'is_editable',
|
|
|
|
'is_text_input', 'remove_blank_target', 'debug_text', 'outer_xml',
|
|
|
|
'tag_name', 'run_js_async', 'rect_on_view', 'is_visible'])
|
2015-08-02 23:48:55 +02:00
|
|
|
def test_vanished(self, elem, code):
|
|
|
|
"""Make sure methods check if the element is vanished."""
|
|
|
|
elem._elem.isNull.return_value = True
|
2016-07-28 13:12:47 +02:00
|
|
|
elem._elem.tagName.return_value = 'span'
|
2015-08-02 23:48:55 +02:00
|
|
|
with pytest.raises(webelem.IsNullError):
|
|
|
|
code(elem)
|
|
|
|
|
|
|
|
def test_str(self, elem):
|
|
|
|
assert str(elem) == 'text'
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('is_null, expected', [
|
2016-06-13 10:49:58 +02:00
|
|
|
(False, "<qutebrowser.browser.webkit.webelem.WebElementWrapper "
|
2015-08-02 23:48:55 +02:00
|
|
|
"html='<fakeelem/>'>"),
|
2016-06-14 04:27:14 +02:00
|
|
|
(True, '<qutebrowser.browser.webkit.webelem.WebElementWrapper '
|
|
|
|
'html=None>'),
|
2015-08-02 23:48:55 +02:00
|
|
|
])
|
|
|
|
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
|
|
|
|
|
2016-07-28 14:25:53 +02:00
|
|
|
def test_not_eq(self):
|
|
|
|
one = get_webelem()
|
|
|
|
two = get_webelem()
|
|
|
|
assert one != two
|
|
|
|
|
|
|
|
def test_eq(self):
|
|
|
|
one = get_webelem()
|
|
|
|
two = webelem.WebElementWrapper(one._elem)
|
|
|
|
assert one == two
|
|
|
|
|
2016-07-28 17:19:52 +02:00
|
|
|
def test_eq_other_type(self):
|
|
|
|
assert get_webelem() != object()
|
|
|
|
|
2015-08-02 23:48:55 +02:00
|
|
|
@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
|
|
|
|
|
2016-07-28 14:25:53 +02:00
|
|
|
@pytest.mark.parametrize('attribute, code', [
|
|
|
|
('webFrame', lambda e: e.frame()),
|
|
|
|
('geometry', lambda e: e.geometry()),
|
|
|
|
('toOuterXml', lambda e: e.outer_xml()),
|
|
|
|
('tagName', lambda e: e.tag_name()),
|
|
|
|
])
|
|
|
|
def test_simple_getters(self, elem, attribute, code):
|
|
|
|
sentinel = object()
|
|
|
|
mock = getattr(elem._elem, attribute)
|
|
|
|
setattr(mock, 'return_value', sentinel)
|
|
|
|
assert code(elem) is sentinel
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('code, method, args', [
|
|
|
|
(lambda e: e.set_inner_xml('foo'), 'setInnerXml', ['foo']),
|
|
|
|
(lambda e: e.set_style_property('foo', 'bar'), 'setStyleProperty',
|
|
|
|
['foo', 'bar']),
|
|
|
|
(lambda e: e.remove_from_document(), 'removeFromDocument', []),
|
|
|
|
])
|
|
|
|
def test_simple_setters(self, elem, code, method, args):
|
|
|
|
code(elem)
|
|
|
|
mock = getattr(elem._elem, method)
|
|
|
|
mock.assert_called_with(*args)
|
|
|
|
|
|
|
|
def test_style_property(self, elem):
|
|
|
|
assert elem.style_property('foo', QWebElement.ComputedStyle) == 'bar'
|
|
|
|
|
|
|
|
def test_document_element(self, stubs):
|
|
|
|
doc_elem = get_webelem()
|
|
|
|
frame = stubs.FakeWebFrame(document_element=doc_elem._elem)
|
|
|
|
elem = get_webelem(frame=frame)
|
|
|
|
|
|
|
|
doc_elem_ret = elem.document_element()
|
|
|
|
assert isinstance(doc_elem_ret, webelem.WebElementWrapper)
|
|
|
|
assert doc_elem_ret == doc_elem
|
|
|
|
|
|
|
|
def test_find_first(self, elem):
|
|
|
|
result = get_webelem()
|
|
|
|
elem._elem.findFirst.return_value = result._elem
|
|
|
|
find_result = elem.find_first('')
|
|
|
|
assert isinstance(find_result, webelem.WebElementWrapper)
|
|
|
|
assert find_result == result
|
|
|
|
|
|
|
|
def test_create_inside(self, elem):
|
|
|
|
child = get_webelem()
|
|
|
|
elem._elem.lastChild.return_value = child._elem
|
|
|
|
assert elem.create_inside('span')._elem is child._elem
|
|
|
|
elem._elem.appendInside.assert_called_with('<span></span>')
|
|
|
|
|
|
|
|
def test_find_first_null(self, elem):
|
|
|
|
nullelem = get_webelem()
|
|
|
|
nullelem._elem.isNull.return_value = True
|
|
|
|
elem._elem.findFirst.return_value = nullelem._elem
|
|
|
|
assert elem.find_first('foo') is None
|
|
|
|
|
2016-07-28 14:46:32 +02:00
|
|
|
@pytest.mark.parametrize('use_js, editable, expected', [
|
|
|
|
(True, 'false', 'js'),
|
|
|
|
(True, 'true', 'nojs'),
|
|
|
|
(False, 'false', 'nojs'),
|
|
|
|
(False, 'true', 'nojs'),
|
|
|
|
])
|
|
|
|
def test_text(self, use_js, editable, expected):
|
|
|
|
elem = get_webelem(attributes={'contenteditable': editable})
|
|
|
|
elem._elem.toPlainText.return_value = 'nojs'
|
|
|
|
elem._elem.evaluateJavaScript.return_value = 'js'
|
|
|
|
assert elem.text(use_js=use_js) == expected
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('use_js, editable, text, uses_js, arg', [
|
|
|
|
(True, 'false', 'foo', True, "this.value='foo'"),
|
|
|
|
(True, 'false', "foo'bar", True, r"this.value='foo\'bar'"),
|
|
|
|
(True, 'true', 'foo', False, 'foo'),
|
|
|
|
(False, 'false', 'foo', False, 'foo'),
|
|
|
|
(False, 'true', 'foo', False, 'foo'),
|
|
|
|
])
|
|
|
|
def test_set_text(self, use_js, editable, text, uses_js, arg):
|
|
|
|
elem = get_webelem(attributes={'contenteditable': editable})
|
|
|
|
elem.set_text(text, use_js=use_js)
|
|
|
|
attr = 'evaluateJavaScript' if uses_js else 'setPlainText'
|
|
|
|
called_mock = getattr(elem._elem, attr)
|
|
|
|
called_mock.assert_called_with(arg)
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('with_cb', [True, False])
|
|
|
|
def test_run_js_async(self, elem, with_cb):
|
|
|
|
cb = mock.Mock(spec={}) if with_cb else None
|
|
|
|
elem._elem.evaluateJavaScript.return_value = 42
|
|
|
|
elem.run_js_async('the_answer();', cb)
|
|
|
|
if with_cb:
|
|
|
|
cb.assert_called_with(42)
|
2016-07-28 14:25:53 +02:00
|
|
|
|
2016-03-30 19:08:37 +02:00
|
|
|
|
2016-04-01 01:30:24 +02:00
|
|
|
class TestRemoveBlankTarget:
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('tagname', ['a', 'area'])
|
|
|
|
@pytest.mark.parametrize('target', ['_self', '_parent', '_top', ''])
|
|
|
|
def test_keep_target(self, tagname, target):
|
|
|
|
elem = get_webelem(tagname=tagname, attributes={'target': target})
|
|
|
|
elem.remove_blank_target()
|
|
|
|
assert elem['target'] == target
|
2016-03-30 19:08:37 +02:00
|
|
|
|
2016-04-01 01:30:24 +02:00
|
|
|
@pytest.mark.parametrize('tagname', ['a', 'area'])
|
|
|
|
def test_no_target(self, tagname):
|
|
|
|
elem = get_webelem(tagname=tagname)
|
2016-03-30 19:08:37 +02:00
|
|
|
elem.remove_blank_target()
|
2016-03-30 23:46:02 +02:00
|
|
|
assert 'target' not in elem
|
2016-03-30 19:08:37 +02:00
|
|
|
|
2016-04-01 01:30:24 +02:00
|
|
|
@pytest.mark.parametrize('tagname', ['a', 'area'])
|
|
|
|
def test_blank_target(self, tagname):
|
|
|
|
elem = get_webelem(tagname=tagname, attributes={'target': '_blank'})
|
|
|
|
elem.remove_blank_target()
|
|
|
|
assert elem['target'] == '_top'
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('tagname', ['a', 'area'])
|
|
|
|
def test_ancestor_blank_target(self, tagname):
|
|
|
|
elem = get_webelem(tagname=tagname, attributes={'target': '_blank'})
|
2016-03-30 19:54:42 +02:00
|
|
|
elem_child = get_webelem(tagname='img', parent=elem._elem)
|
2016-03-30 19:08:37 +02:00
|
|
|
elem_child._elem.encloseWith(elem._elem)
|
|
|
|
elem_child.remove_blank_target()
|
2016-04-01 01:30:24 +02:00
|
|
|
assert elem['target'] == '_top'
|
2016-03-30 19:08:37 +02:00
|
|
|
|
2016-04-01 01:30:24 +02:00
|
|
|
@pytest.mark.parametrize('depth', [1, 5, 10])
|
|
|
|
def test_no_link(self, depth):
|
|
|
|
elem = [None] * depth
|
2016-03-31 10:25:44 +02:00
|
|
|
elem[0] = get_webelem(tagname='div')
|
2016-04-01 01:30:24 +02:00
|
|
|
for i in range(1, depth):
|
2016-07-28 10:50:14 +02:00
|
|
|
elem[i] = get_webelem(tagname='div', parent=elem[i-1]._elem)
|
2016-03-31 10:25:44 +02:00
|
|
|
elem[i]._elem.encloseWith(elem[i-1]._elem)
|
2016-04-01 01:30:24 +02:00
|
|
|
elem[-1].remove_blank_target()
|
|
|
|
for i in range(depth):
|
2016-03-31 10:25:44 +02:00
|
|
|
assert 'target' not in elem[i]
|
|
|
|
|
2015-08-03 23:22:40 +02:00
|
|
|
|
2015-08-02 22:10:00 +02:00
|
|
|
class TestIsVisible:
|
2014-05-12 11:41:35 +02:00
|
|
|
|
2015-08-02 22:10:00 +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)
|
|
|
|
|
2015-08-02 22:10:00 +02:00
|
|
|
def test_invalid_invisible(self, frame):
|
2014-05-12 11:41:35 +02:00
|
|
|
"""Test elements with an invalid geometry which are invisible."""
|
2015-08-02 22:10:00 +02:00
|
|
|
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
|
2015-08-02 22:10:00 +02:00
|
|
|
assert not elem.is_visible(frame)
|
2014-05-05 15:16:27 +02:00
|
|
|
|
2015-08-02 22:10:00 +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.
|
|
|
|
"""
|
2015-08-02 22:10:00 +02:00
|
|
|
elem = get_webelem(QRect(10, 10, 0, 0), frame)
|
2015-04-03 23:36:35 +02:00
|
|
|
assert not elem.geometry().isValid()
|
2015-08-02 22:10:00 +02:00
|
|
|
assert elem.is_visible(frame)
|
2014-05-12 11:07:08 +02:00
|
|
|
|
2015-08-02 22:10:00 +02:00
|
|
|
@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
|
2014-05-12 11:07:08 +02:00
|
|
|
|
|
|
|
|
2015-04-04 17:05:44 +02:00
|
|
|
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
|
|
|
|
2015-08-02 22:10:00 +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 #
|
|
|
|
#********** #
|
2015-08-02 22:10:00 +02:00
|
|
|
#*e * elems[0]: 0, 0 in iframe (visible)
|
2015-03-26 07:08:13 +01:00
|
|
|
#* * #
|
2015-08-02 22:10:00 +02:00
|
|
|
#* e * elems[1]: 20,90 in iframe (visible)
|
2015-03-26 07:08:13 +01:00
|
|
|
#********** #
|
|
|
|
0,110 #. .100,110 #
|
|
|
|
#. . #
|
2015-08-02 22:10:00 +02:00
|
|
|
#. e . elems[2]: 20,150 in iframe (not visible)
|
2015-03-26 07:08:13 +01:00
|
|
|
#.......... #
|
2015-08-02 22:10:00 +02:00
|
|
|
# e elems[3]: 30, 180 in main frame (visible)
|
2015-03-26 07:08:13 +01:00
|
|
|
# #
|
|
|
|
# frame #
|
|
|
|
##############################
|
|
|
|
300, 0 300, 300
|
2015-08-02 22:10:00 +02:00
|
|
|
|
|
|
|
Returns an Objects namedtuple with frame/iframe/elems attributes.
|
2014-05-12 12:16:41 +02:00
|
|
|
"""
|
2015-08-02 22:10:00 +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."""
|
2015-08-02 22:10:00 +02:00
|
|
|
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
|
|
|
|
2015-08-02 22:10:00 +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."""
|
2015-08-02 22:10:00 +02:00
|
|
|
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."""
|
2015-08-02 22:10:00 +02:00
|
|
|
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):
|
2016-02-10 19:18:47 +01:00
|
|
|
"""Set up the following base situation.
|
2015-08-10 19:37:16 +02:00
|
|
|
|
|
|
|
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-02 22:10:00 +02:00
|
|
|
|
2015-08-03 23:22:40 +02:00
|
|
|
class TestRectOnView:
|
2015-08-02 22:10:00 +02:00
|
|
|
|
2016-06-06 15:19:27 +02:00
|
|
|
@pytest.fixture(autouse=True)
|
|
|
|
def stubbed_config(self, config_stub, monkeypatch):
|
|
|
|
"""Add a zoom-text-only fake config value.
|
|
|
|
|
|
|
|
This is needed for all the tests calling rect_on_view or is_visible.
|
|
|
|
"""
|
|
|
|
config_stub.data = {'ui': {'zoom-text-only': 'true'}}
|
2016-06-13 10:49:58 +02:00
|
|
|
monkeypatch.setattr('qutebrowser.browser.webkit.webelem.config',
|
|
|
|
config_stub)
|
2016-06-06 15:19:27 +02:00
|
|
|
return config_stub
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('js_rect', [
|
|
|
|
None, # real geometry via getElementRects
|
|
|
|
{}, # no geometry at all via getElementRects
|
|
|
|
# unusable geometry via getElementRects
|
|
|
|
{'length': '1', '0': {'width': 0, 'height': 0, 'x': 0, 'y': 0}},
|
|
|
|
])
|
|
|
|
def test_simple(self, stubs, js_rect):
|
2015-08-03 23:22:40 +02:00
|
|
|
geometry = QRect(5, 5, 4, 4)
|
|
|
|
frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100))
|
2016-06-06 15:19:27 +02:00
|
|
|
elem = get_webelem(geometry, frame, js_rect_return=js_rect)
|
2015-08-03 23:22:40 +02:00
|
|
|
assert elem.rect_on_view() == QRect(5, 5, 4, 4)
|
2015-08-02 22:10:00 +02:00
|
|
|
|
2016-06-06 15:19:27 +02:00
|
|
|
@pytest.mark.parametrize('js_rect', [None, {}])
|
|
|
|
def test_scrolled(self, stubs, js_rect):
|
2015-08-03 23:22:40 +02:00
|
|
|
geometry = QRect(20, 20, 4, 4)
|
|
|
|
frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100),
|
|
|
|
scroll=QPoint(10, 10))
|
2016-06-06 15:19:27 +02:00
|
|
|
elem = get_webelem(geometry, frame, js_rect_return=js_rect)
|
2015-08-03 23:22:40 +02:00
|
|
|
assert elem.rect_on_view() == QRect(20 - 10, 20 - 10, 4, 4)
|
|
|
|
|
2016-06-06 15:19:27 +02:00
|
|
|
@pytest.mark.parametrize('js_rect', [None, {}])
|
|
|
|
def test_iframe(self, stubs, js_rect):
|
2015-08-03 23:22:40 +02:00
|
|
|
"""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())
|
2016-06-06 15:19:27 +02:00
|
|
|
elem = get_webelem(QRect(20, 90, 10, 10), iframe,
|
|
|
|
js_rect_return=js_rect)
|
2015-08-03 23:22:40 +02:00
|
|
|
assert elem.rect_on_view() == QRect(20, 10 + 90, 10, 10)
|
|
|
|
|
2016-06-06 15:19:27 +02:00
|
|
|
@pytest.mark.parametrize('js_rect', [None, {}])
|
|
|
|
def test_passed_geometry(self, stubs, js_rect):
|
2015-08-03 23:22:40 +02:00
|
|
|
"""Make sure geometry isn't called when a geometry is passed."""
|
2016-06-06 11:56:15 +02:00
|
|
|
frame = stubs.FakeWebFrame(QRect(0, 0, 200, 200))
|
2016-07-28 10:34:51 +02:00
|
|
|
elem = get_webelem(frame=frame, js_rect_return=js_rect)
|
2015-08-03 23:22:40 +02:00
|
|
|
rect = QRect(10, 20, 30, 40)
|
2016-07-28 10:34:51 +02:00
|
|
|
assert elem.rect_on_view(elem_geometry=rect) == rect
|
2016-07-28 10:50:49 +02:00
|
|
|
assert not elem._elem.geometry.called
|
2015-08-03 23:22:40 +02:00
|
|
|
|
2016-06-06 15:19:27 +02:00
|
|
|
@pytest.mark.parametrize('js_rect', [None, {}])
|
|
|
|
@pytest.mark.parametrize('zoom_text_only', [True, False])
|
|
|
|
@pytest.mark.parametrize('adjust_zoom', [True, False])
|
|
|
|
def test_zoomed(self, stubs, config_stub, js_rect, zoom_text_only,
|
|
|
|
adjust_zoom):
|
|
|
|
"""Make sure the coordinates are adjusted when zoomed."""
|
|
|
|
config_stub.data = {'ui': {'zoom-text-only': zoom_text_only}}
|
|
|
|
geometry = QRect(10, 10, 4, 4)
|
|
|
|
frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100), zoom=0.5)
|
|
|
|
elem = get_webelem(geometry, frame, js_rect_return=js_rect,
|
|
|
|
zoom_text_only=zoom_text_only)
|
|
|
|
rect = elem.rect_on_view(adjust_zoom=adjust_zoom)
|
|
|
|
if zoom_text_only or (js_rect is None and adjust_zoom):
|
|
|
|
assert rect == QRect(10, 10, 4, 4)
|
|
|
|
else:
|
|
|
|
assert rect == QRect(20, 20, 8, 8)
|
|
|
|
|
2015-08-03 23:22:40 +02:00
|
|
|
|
|
|
|
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',
|
2015-08-04 12:07:49 +02:00
|
|
|
'\x00': r'\x00',
|
|
|
|
'hellö': 'hellö',
|
|
|
|
'☃': '☃',
|
|
|
|
'\x80Ā': '\x80Ā',
|
|
|
|
'𐀀\x00𐀀\x00': r'𐀀\x00𐀀\x00',
|
2015-11-25 18:14:52 +01:00
|
|
|
'𐀀\ufeff': r'𐀀\ufeff',
|
2015-11-25 18:47:16 +01:00
|
|
|
'\ufeff': r'\ufeff',
|
2015-11-25 10:32:22 +01:00
|
|
|
# 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?
|
|
|
|
|
2016-01-25 22:15:31 +01:00
|
|
|
@pytest.mark.parametrize('before, after', sorted(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
|
|
|
|
|
2015-08-04 08:49:42 +02:00
|
|
|
def _test_escape(self, text, qtbot, webframe):
|
2015-08-03 23:22:40 +02:00
|
|
|
"""Helper function for test_real_escape*."""
|
2015-08-04 08:49:42 +02:00
|
|
|
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
|
2016-07-05 08:34:03 +02:00
|
|
|
older PyQt versions in some corner cases, we load an HTML file which
|
2015-08-04 08:49:42 +02:00
|
|
|
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)
|
|
|
|
|
2016-01-08 09:49:06 +01:00
|
|
|
with qtbot.waitSignal(webframe.loadFinished) as blocker:
|
2015-08-04 08:49:42 +02:00
|
|
|
webframe.setHtml(html_source)
|
2015-12-17 07:43:57 +01:00
|
|
|
assert blocker.args == [True]
|
2015-08-04 08:49:42 +02:00
|
|
|
|
|
|
|
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)
|
2015-08-04 08:49:42 +02:00
|
|
|
result = webframe.evaluateJavaScript('"{}";'.format(escaped))
|
|
|
|
assert result == text
|
2015-08-03 23:22:40 +02:00
|
|
|
|
2016-01-25 22:15:31 +01:00
|
|
|
@pytest.mark.parametrize('text', sorted(TESTS), ids=repr)
|
2015-08-04 08:49:42 +02:00
|
|
|
def test_real_escape(self, webframe, qtbot, text):
|
2015-08-03 23:22:40 +02:00
|
|
|
"""Test javascript escaping with a real QWebPage."""
|
2015-08-04 08:49:42 +02:00
|
|
|
self._test_escape(text, qtbot, webframe)
|
2015-08-03 23:22:40 +02:00
|
|
|
|
2016-07-12 09:39:43 +02:00
|
|
|
@pytest.mark.qt_log_ignore('^OpenType support missing for script',
|
2016-01-20 08:06:36 +01:00
|
|
|
extend=True)
|
2015-10-05 08:11:26 +02:00
|
|
|
@hypothesis.given(hypothesis.strategies.text())
|
2015-08-04 08:49:42 +02:00
|
|
|
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."""
|
2015-08-04 08:49:42 +02:00
|
|
|
# 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
|
|
|
|
|
|
|
|
2015-04-04 17:05:44 +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
|
|
|
|
|
|
|
|
2015-04-04 17:05:44 +02:00
|
|
|
class TestIsEditable:
|
2014-06-23 20:31:47 +02:00
|
|
|
|
|
|
|
"""Tests for is_editable."""
|
|
|
|
|
2015-04-08 05:46:02 +02:00
|
|
|
@pytest.fixture
|
2015-05-18 23:32:01 +02:00
|
|
|
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."""
|
2015-05-07 07:58:22 +02:00
|
|
|
config_stub.data = {'input': {}}
|
2016-06-13 10:49:58 +02:00
|
|
|
monkeypatch.setattr('qutebrowser.browser.webkit.webelem.config',
|
|
|
|
config_stub)
|
2015-05-07 07:58:22 +02:00
|
|
|
return config_stub
|
2014-06-23 20:31:47 +02:00
|
|
|
|
2015-08-02 22:10:00 +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),
|
2015-08-02 22:10:00 +02:00
|
|
|
])
|
|
|
|
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):
|
2015-08-02 22:10:00 +02:00
|
|
|
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 22:10:00 +02:00
|
|
|
])
|
2015-08-02 23:48:55 +02:00
|
|
|
def test_is_editable_plugin(self, stubbed_config, setting, tagname,
|
|
|
|
attributes, editable):
|
2015-08-02 22:10:00 +02:00
|
|
|
stubbed_config.data['input']['insert-mode-on-plugins'] = setting
|
|
|
|
elem = get_webelem(tagname=tagname, attributes=attributes)
|
|
|
|
assert elem.is_editable() == editable
|