1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import time
from selenium.common import exceptions
from selenium import webdriver
from selenium.webdriver.common import by
from selenium.webdriver.common import desired_capabilities as dc
from selenium.webdriver.remote import webelement
class ElementNotReloadableException(Exception):
"""Raised when reload is not possible."""
pass
class WrapperFindOverride(object):
"""Mixin for overriding find_element methods."""
def find_element(self, by=by.By.ID, value=None):
web_el = super(WrapperFindOverride, self).find_element(by, value)
return WebElementWrapper(web_el.parent, web_el.id, (by, value), self)
def find_elements(self, by=by.By.ID, value=None):
web_els = super(WrapperFindOverride, self).find_elements(by, value)
result = []
for index, web_el in enumerate(web_els):
result.append(WebElementWrapper(web_el.parent, web_el.id,
(by, value), self, index))
return result
class WebElementWrapper(WrapperFindOverride, webelement.WebElement):
"""WebElement class wrapper.
WebElement wrapper that catches the StaleElementReferenceException and
tries to reload the element by sending request to its source element
(element that created actual element) for reload, in case that source
element needs to be reloaded as well, it asks its parent till web driver
is reached. In case driver was reached and did not manage to find the
element it is probable that programmer made a mistake and actual
StaleElementReferenceException is raised.
"""
STALE_ELEMENT_REFERENCE_WAIT = 0.5
STALE_ELEMENT_REFERENCE_MAX_TRY = 10
def __init__(self, parent, id_, locator, src_element, index=None):
super(WebElementWrapper, self).__init__(parent, id_)
self.locator = locator
self.src_element = src_element
# StaleElementReferenceException occurrence counter
self.stale_reference_occurrence = 0
# storing if web element reload succeed or not
# in case of fail StaleElementReferenceException is raised
self.reload_failed = False
# in case element was looked up previously via find_elements
# we need his position in the returned list
self.index = index
# if reloading of some other web element is in progress
# StaleElementReferenceException is not raised within current context
self.web_element_reload = False
def reload_request(self, locator, index=None):
self.web_element_reload = True
try:
# element was found out via find_elements
if index is not None:
web_els = self.src_element.find_elements(*locator)
web_el = web_els[index]
else:
web_el = self.src_element.find_element(*locator)
except (exceptions.NoSuchElementException, IndexError):
return False
self.web_element_reload = False
return web_el
def _reload_element(self):
"""Method for starting reload process on current instance."""
web_el = self.src_element.reload_request(self.locator, self.index)
if not web_el:
return
self._parent = web_el.parent
self._id = web_el.id
def _execute(self, command, params=None):
"""Overriding in order to catch StaleElementReferenceException."""
result = None
while True:
try:
result = super(WebElementWrapper, self)._execute(command,
params)
break
except exceptions.StaleElementReferenceException:
# in case we reach the limit STALE_ELEMENT_REFERENCE_MAX_TRY
# it is very probable that it is programmer fault
if self.reload_failed or self.stale_reference_occurrence \
> self.STALE_ELEMENT_REFERENCE_MAX_TRY:
raise
# this is either programmer fault
# (bad logic in accessing elements)
# or web page content is been loaded via ajax, let's go with
# the second one and wait STALE_ELEMENT_REFERENCE_WAIT till
# the assumed page content is loaded and try
# to execute the whole process
# STALE_ELEMENT_REFERENCE_MAX_TRY times in case of failures
time.sleep(self.STALE_ELEMENT_REFERENCE_WAIT)
# try to reload the web element
# if result is false it means that request has gone to the
# driver and he did not find the element -> must be programmer
# fault, because it seems we are on entirely different page
try:
self._reload_element()
except ElementNotReloadableException:
# In case this element was responsible only for loading
# some other element raise the exception further
if self.web_element_reload:
raise
else:
self.reload_failed = True
# increment occurrences
self.stale_reference_occurrence += 1
# reset counter
self.stale_reference_occurrence = 0
return result
class WebDriverWrapper(WrapperFindOverride, webdriver.Firefox):
"""Wrapper for webdriver to return WebElementWrapper on find_element."""
def __init__(self, logging_prefs=None, capabilities=None, **kwargs):
if capabilities is None:
capabilities = dc.DesiredCapabilities.FIREFOX
if logging_prefs is None:
logging_prefs = {'browser': 'ALL'}
capabilities['loggingPrefs'] = logging_prefs
super(WebDriverWrapper, self).__init__(capabilities=capabilities,
**kwargs)
def reload_request(self, locator, index):
try:
# element was found out via find_elements
if index is not None:
web_els = self.find_elements(*locator)
web_el = web_els[index]
else:
web_el = self.find_element(*locator)
return web_el
except (exceptions.NoSuchElementException, IndexError):
raise ElementNotReloadableException()
|