Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
CIRCLE
/
cloud
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
94
Merge Requests
10
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
abcfee03
authored
Apr 02, 2015
by
Csók Tamás
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
selenium: stability upgrades, fallback action chains
parent
dc0834bd
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
202 additions
and
126 deletions
+202
-126
circle/dashboard/tests/selenium/util.py
+202
-126
No files found.
circle/dashboard/tests/selenium/util.py
View file @
abcfee03
...
@@ -24,7 +24,8 @@ import time
...
@@ -24,7 +24,8 @@ import time
import
urlparse
import
urlparse
from
selenium.common.exceptions
import
(
from
selenium.common.exceptions
import
(
NoSuchElementException
,
StaleElementReferenceException
)
NoSuchElementException
,
StaleElementReferenceException
,
TimeoutException
)
from
selenium.webdriver.common.by
import
By
from
selenium.webdriver.common.by
import
By
from
selenium.webdriver.support
import
expected_conditions
as
ec
from
selenium.webdriver.support
import
expected_conditions
as
ec
from
selenium.webdriver.support.select
import
Select
from
selenium.webdriver.support.select
import
Select
...
@@ -35,7 +36,131 @@ from .config import SeleniumConfig
...
@@ -35,7 +36,131 @@ from .config import SeleniumConfig
logger
=
logging
.
getLogger
(
SeleniumConfig
.
logger_name
)
logger
=
logging
.
getLogger
(
SeleniumConfig
.
logger_name
)
class
CircleSeleniumMixin
(
object
):
class
SeleniumMixin
(
object
):
def
list_options
(
self
,
select
):
try
:
option_dic
=
{}
select
=
Select
(
select
)
for
option
in
select
.
options
:
key
=
option
.
get_attribute
(
'value'
)
if
key
is
not
None
and
key
:
option_dic
[
key
]
=
[
option
.
text
]
return
option_dic
except
:
logger
.
exception
(
"Selenium cannot list the"
" select possibilities"
)
raise
Exception
(
'Cannot list the select possibilities'
)
def
select_option
(
self
,
select
,
what
=
None
):
"""
From an HTML select imput type try to choose the specified one.
Select is a selenium web element type. What represent both the
text of the option and it's ID.
"""
try
:
my_choice
=
None
options
=
self
.
list_options
(
select
)
select
=
Select
(
select
)
if
what
is
not
None
:
for
key
,
value
in
options
.
iteritems
():
if
what
in
key
:
my_choice
=
key
else
:
if
isinstance
(
value
,
list
):
for
single_value
in
value
:
if
what
in
single_value
:
my_choice
=
key
else
:
if
what
in
value
:
my_choice
=
key
if
my_choice
is
None
:
my_choose_list
=
options
.
keys
()
my_choice
=
my_choose_list
[
random
.
randint
(
0
,
len
(
my_choose_list
)
-
1
)]
select
.
select_by_value
(
my_choice
)
except
:
logger
.
exception
(
"Selenium cannot select the chosen one"
)
raise
Exception
(
'Cannot select the chosen one'
)
def
get_link_by_href
(
self
,
target_href
,
attributes
=
None
):
try
:
links
=
self
.
driver
.
find_elements_by_tag_name
(
'a'
)
for
link
in
links
:
href
=
link
.
get_attribute
(
'href'
)
if
href
is
not
None
and
href
:
if
target_href
in
href
:
perfect_fit
=
True
if
isinstance
(
attributes
,
dict
):
for
key
,
target_value
in
attributes
.
iteritems
():
attr_check
=
link
.
get_attribute
(
key
)
if
attr_check
is
not
None
and
attr_check
:
if
target_value
not
in
attr_check
:
perfect_fit
=
False
if
perfect_fit
:
return
link
except
:
logger
.
exception
(
"Selenium cannot find the href=
%
s link"
%
target_href
)
raise
Exception
(
'Cannot find the requested href'
)
def
click_on_link
(
self
,
link
):
"""
There are situations when selenium built in click() function
doesn't work as intended, that's when this function is used.
Fires a click event via javascript injection.
"""
try
:
# Javascript function to simulate a click on a link
javascript
=
"""
var link = arguments[0];
var cancelled = false;
if(document.createEvent) {
var event = document.createEvent("MouseEvents");
event.initMouseEvent(
"click", true, true, window, 0, 0, 0, 0, 0,
false,false,false,false,0,null);
cancelled = !link.dispatchEvent(event);
} else if(link.fireEvent) {
cancelled = !link.fireEvent("onclick");
} if (!cancelled) {
window.location = link.href;
}"""
self
.
driver
.
execute_script
(
javascript
,
link
)
except
:
logger
.
exception
(
"Selenium cannot inject javascript to the page"
)
raise
Exception
(
'Cannot inject javascript to the page'
)
def
get_text
(
self
,
node
,
tag
):
"""
There are some cases where selenium default WebElement text()
method returns less then it actually could contain. Solving that
here is a simple regular expression. Give the closest html element
then specify the html tag of the enclosed text.
"""
text
=
""
try
:
text_whole
=
re
.
search
(
r'<
%(tag)
s[^>]*>([^<]+)</
%(tag)
s>'
%
{
'tag'
:
tag
},
node
.
get_attribute
(
"outerHTML"
))
.
group
()
text_parts
=
text_whole
.
splitlines
()
for
part
in
text_parts
:
if
'<'
not
in
part
and
'>'
not
in
part
:
text
+=
part
text
=
text
.
replace
(
" "
,
""
)
except
:
return
node
.
text
if
len
(
node
.
text
)
>=
len
(
text
):
text
=
node
.
text
else
:
logger
.
warning
(
"Better text found which is '
%
s'"
%
text
)
return
text
.
strip
()
class
CircleSeleniumMixin
(
SeleniumMixin
):
def
login
(
self
,
location
=
None
):
def
login
(
self
,
location
=
None
):
driver
=
self
.
driver
driver
=
self
.
driver
if
location
is
None
:
if
location
is
None
:
...
@@ -76,7 +201,8 @@ class CircleSeleniumMixin(object):
...
@@ -76,7 +201,8 @@ class CircleSeleniumMixin(object):
logger
.
exception
(
"Selenium cannot find the form controls"
)
logger
.
exception
(
"Selenium cannot find the form controls"
)
raise
Exception
(
'Cannot find the form controls'
)
raise
Exception
(
'Cannot find the form controls'
)
def
wait_and_accept_operation
(
self
,
argument
=
None
):
def
wait_and_accept_operation
(
self
,
argument
=
None
,
try_wait
=
None
,
fallback_url
=
None
):
"""
"""
Accepts the operation confirmation pop up window.
Accepts the operation confirmation pop up window.
Fills out the text inputs before accepting if argument is given.
Fills out the text inputs before accepting if argument is given.
...
@@ -97,20 +223,56 @@ class CircleSeleniumMixin(object):
...
@@ -97,20 +223,56 @@ class CircleSeleniumMixin(object):
form
.
clear
()
form
.
clear
()
form
.
send_keys
(
argument
)
form
.
send_keys
(
argument
)
accept
.
click
()
accept
.
click
()
if
try_wait
is
not
None
:
try
:
WebDriverWait
(
self
.
driver
,
self
.
conf
.
wait_max_sec
)
.
until
(
ec
.
visibility_of_element_located
((
By
.
CSS_SELECTOR
,
try_wait
)))
except
TimeoutException
:
# Try to submit to form using other method
try
:
accept
=
WebDriverWait
(
self
.
driver
,
self
.
conf
.
wait_max_sec
)
.
until
(
ec
.
element_to_be_clickable
((
By
.
CLASS_NAME
,
"modal-accept"
)))
accept
.
find_element_by_css_selector
(
"form[method*='POST']"
)
.
submit
()
except
:
logger
.
exception
(
"Selenium couldn't find the modal at retry"
)
raise
Exception
(
"Cannot find the modal"
)
except
:
logger
.
exception
(
"Selenium couldn't find the specified css element"
)
raise
Exception
(
"Cannot find the css element"
)
except
:
except
:
logger
.
exception
(
"Selenium cannot accept the"
logger
.
exception
(
"Selenium cannot accept the"
" operation confirmation"
)
" operation confirmation"
)
raise
Exception
(
if
fallback_url
is
not
None
:
'Cannot accept the operation confirmation'
)
logger
.
warning
(
"However error was anticipated falling back to
%(url)
s"
%
{
'url'
:
fallback_url
})
self
.
driver
.
get
(
fallback_url
)
self
.
wait_and_accept_operation
(
argument
,
try_wait
)
else
:
self
.
driver
.
save_screenshot
(
'error_at_try_wait.png'
)
raise
Exception
(
'Cannot accept the operation confirmation'
)
def
save_template_from_vm
(
self
,
name
):
def
save_template_from_vm
(
self
,
name
):
try
:
try
:
url_base
=
urlparse
.
urlparse
(
self
.
driver
.
current_url
)
url_save
=
(
"
%(host)
s
%(url)
s"
%
{
'host'
:
self
.
conf
.
host
,
'url'
:
urlparse
.
urljoin
(
url_base
.
path
,
url_base
.
query
)})
WebDriverWait
(
self
.
driver
,
self
.
conf
.
wait_max_sec
)
.
until
(
WebDriverWait
(
self
.
driver
,
self
.
conf
.
wait_max_sec
)
.
until
(
ec
.
element_to_be_clickable
((
ec
.
element_to_be_clickable
((
By
.
CSS_SELECTOR
,
By
.
CSS_SELECTOR
,
"a[href$='/op/deploy/']"
)))
"a[href$='/op/deploy/']"
)))
self
.
click_on_link
(
self
.
get_link_by_href
(
"/op/deploy/"
))
self
.
click_on_link
(
self
.
get_link_by_href
(
"/op/deploy/"
))
self
.
wait_and_accept_operation
()
fallback_url
=
"
%
sop/deploy/"
%
url_save
self
.
wait_and_accept_operation
(
try_wait
=
"a[href$='/op/shut_off/']"
,
fallback_url
=
fallback_url
)
recent_deploy
=
self
.
recently
(
self
.
get_timeline_elements
(
recent_deploy
=
self
.
recently
(
self
.
get_timeline_elements
(
"vm.Instance.deploy"
))
"vm.Instance.deploy"
))
if
not
self
.
check_operation_result
(
if
not
self
.
check_operation_result
(
...
@@ -123,10 +285,9 @@ class CircleSeleniumMixin(object):
...
@@ -123,10 +285,9 @@ class CircleSeleniumMixin(object):
ec
.
element_to_be_clickable
((
ec
.
element_to_be_clickable
((
By
.
CSS_SELECTOR
,
By
.
CSS_SELECTOR
,
"a[href$='/op/shut_off/']"
))))
"a[href$='/op/shut_off/']"
))))
self
.
wait_and_accept_operation
()
fallback_url
=
"
%
sop/shut_off/"
%
url_save
WebDriverWait
(
self
.
driver
,
self
.
conf
.
wait_max_sec
)
.
until
(
self
.
wait_and_accept_operation
(
ec
.
element_to_be_clickable
((
try_wait
=
"a[href$='/op/deploy/']"
,
fallback_url
=
fallback_url
)
By
.
CSS_SELECTOR
,
"a[href$='/op/deploy/']"
)))
recent_shut_off
=
self
.
recently
(
self
.
get_timeline_elements
(
recent_shut_off
=
self
.
recently
(
self
.
get_timeline_elements
(
"vm.Instance.shut_off"
))
"vm.Instance.shut_off"
))
if
not
self
.
check_operation_result
(
if
not
self
.
check_operation_result
(
...
@@ -140,6 +301,8 @@ class CircleSeleniumMixin(object):
...
@@ -140,6 +301,8 @@ class CircleSeleniumMixin(object):
By
.
CSS_SELECTOR
,
By
.
CSS_SELECTOR
,
"a[href$='/op/save_as_template/']"
))))
"a[href$='/op/save_as_template/']"
))))
self
.
wait_and_accept_operation
(
name
)
self
.
wait_and_accept_operation
(
name
)
logger
.
warning
(
"Selenium created
%(name)
s template"
%
{
'name'
:
name
})
return
name
return
name
except
:
except
:
logger
.
exception
(
"Selenium cannot save a vm as a template"
)
logger
.
exception
(
"Selenium cannot save a vm as a template"
)
...
@@ -150,7 +313,7 @@ class CircleSeleniumMixin(object):
...
@@ -150,7 +313,7 @@ class CircleSeleniumMixin(object):
method
=
None
,
op_system
=
None
,
lease
=
None
,
method
=
None
,
op_system
=
None
,
lease
=
None
,
network
=
"vm"
):
network
=
"vm"
):
if
name
is
None
:
if
name
is
None
:
name
=
"
template_
new_
%
s"
%
self
.
conf
.
client_name
name
=
"new_
%
s"
%
self
.
conf
.
client_name
if
op_system
is
None
:
if
op_system
is
None
:
op_system
=
"!os
%
s"
%
self
.
conf
.
client_name
op_system
=
"!os
%
s"
%
self
.
conf
.
client_name
try
:
try
:
...
@@ -213,7 +376,9 @@ class CircleSeleniumMixin(object):
...
@@ -213,7 +376,9 @@ class CircleSeleniumMixin(object):
templates
=
template_table
.
find_elements_by_css_selector
(
"td.name"
)
templates
=
template_table
.
find_elements_by_css_selector
(
"td.name"
)
found_template_ids
=
[]
found_template_ids
=
[]
for
template
in
templates
:
for
template
in
templates
:
if
name
is
None
or
name
in
template
.
text
:
# Little magic to outsmart accented naming errors
template_name
=
self
.
get_text
(
template
,
"a"
)
if
name
is
None
or
name
in
template_name
:
try
:
try
:
template_link
=
template
.
find_element_by_css_selector
(
template_link
=
template
.
find_element_by_css_selector
(
css_selector_of_a_template
)
css_selector_of_a_template
)
...
@@ -223,12 +388,21 @@ class CircleSeleniumMixin(object):
...
@@ -223,12 +388,21 @@ class CircleSeleniumMixin(object):
found_template_ids
.
append
(
template_id
)
found_template_ids
.
append
(
template_id
)
logger
.
warning
(
"Found '
%(name)
s' "
logger
.
warning
(
"Found '
%(name)
s' "
"template's ID as
%(id)
s"
%
{
"template's ID as
%(id)
s"
%
{
'name'
:
template
.
text
,
'name'
:
template
_name
,
'id'
:
template_id
})
'id'
:
template_id
})
except
NoSuchElementException
:
except
NoSuchElementException
:
pass
pass
except
:
except
:
raise
raise
else
:
logger
.
warning
(
"Searching for
%(searched)
s so"
"
%(name)
s is dismissed"
%
{
'searched'
:
name
,
'name'
:
template_name
})
logger
.
warning
(
"Dismissed template html code:
%(code)
s"
%
{
'code'
:
template
.
get_attribute
(
"outerHTML"
)})
if
not
found_template_ids
and
name
is
not
None
:
if
not
found_template_ids
and
name
is
not
None
:
logger
.
warning
(
"Selenium could not find the specified "
logger
.
warning
(
"Selenium could not find the specified "
"
%(name)
s template in the list"
%
{
"
%(name)
s template in the list"
%
{
...
@@ -339,7 +513,7 @@ class CircleSeleniumMixin(object):
...
@@ -339,7 +513,7 @@ class CircleSeleniumMixin(object):
code_text
=
code
code_text
=
code
css_activity_selector
=
(
"div[data-activity-code="
css_activity_selector
=
(
"div[data-activity-code="
"'
%(code)
s']"
%
{
"'
%(code)
s']"
%
{
'code'
:
code
_text
})
'code'
:
code
})
self
.
click_on_link
(
WebDriverWait
(
self
.
click_on_link
(
WebDriverWait
(
self
.
driver
,
self
.
conf
.
wait_max_sec
)
.
until
(
self
.
driver
,
self
.
conf
.
wait_max_sec
)
.
until
(
ec
.
element_to_be_clickable
((
ec
.
element_to_be_clickable
((
...
@@ -378,7 +552,7 @@ class CircleSeleniumMixin(object):
...
@@ -378,7 +552,7 @@ class CircleSeleniumMixin(object):
def
create_template_from_base
(
self
,
delete_disk
=
True
,
name
=
None
):
def
create_template_from_base
(
self
,
delete_disk
=
True
,
name
=
None
):
try
:
try
:
if
name
is
None
:
if
name
is
None
:
name
=
"
template_from_base
_
%
s"
%
self
.
conf
.
client_name
name
=
"
from
_
%
s"
%
self
.
conf
.
client_name
self
.
driver
.
get
(
'
%
s/dashboard/template/choose/'
%
self
.
conf
.
host
)
self
.
driver
.
get
(
'
%
s/dashboard/template/choose/'
%
self
.
conf
.
host
)
choice_list
=
[]
choice_list
=
[]
choices
=
self
.
driver
.
find_elements_by_css_selector
(
choices
=
self
.
driver
.
find_elements_by_css_selector
(
...
@@ -402,14 +576,8 @@ class CircleSeleniumMixin(object):
...
@@ -402,14 +576,8 @@ class CircleSeleniumMixin(object):
if
len
(
disk_list
)
>
0
:
if
len
(
disk_list
)
>
0
:
self
.
click_on_link
(
self
.
click_on_link
(
self
.
get_link_by_href
(
"/op/remove_disk/"
))
self
.
get_link_by_href
(
"/op/remove_disk/"
))
self
.
wait_and_accept_operation
()
self
.
wait_and_accept_operation
(
WebDriverWait
(
self
.
driver
,
self
.
conf
.
wait_max_sec
)
.
until
(
try_wait
=
"a[href*='#activity']"
)
ec
.
visibility_of_element_located
((
By
.
ID
,
"_activity"
)))
self
.
click_on_link
(
WebDriverWait
(
self
.
driver
,
self
.
conf
.
wait_max_sec
)
.
until
(
ec
.
element_to_be_clickable
((
By
.
CSS_SELECTOR
,
"a[href*='#activity']"
))))
recent_remove_disk
=
self
.
recently
(
recent_remove_disk
=
self
.
recently
(
self
.
get_timeline_elements
(
self
.
get_timeline_elements
(
"vm.Instance.remove_disk"
))
"vm.Instance.remove_disk"
))
...
@@ -454,10 +622,16 @@ class CircleSeleniumMixin(object):
...
@@ -454,10 +622,16 @@ class CircleSeleniumMixin(object):
'vm-create-template-summary'
)
'vm-create-template-summary'
)
choice
=
random
.
randint
(
0
,
len
(
vm_list
)
-
1
)
choice
=
random
.
randint
(
0
,
len
(
vm_list
)
-
1
)
vm_list
[
choice
]
.
click
()
vm_list
[
choice
]
.
click
()
WebDriverWait
(
self
.
driver
,
self
.
conf
.
wait_max_sec
)
.
until
(
try
:
ec
.
element_to_be_clickable
((
WebDriverWait
(
self
.
driver
,
self
.
conf
.
wait_max_sec
)
.
until
(
By
.
CSS_SELECTOR
,
ec
.
element_to_be_clickable
((
"button[class*='vm-create-start']"
)))
.
click
()
By
.
CLASS_NAME
,
"vm-create-start"
)))
.
click
()
except
TimeoutException
:
# Selenium can time out not findig it even though it is present
self
.
driver
.
find_element_by_tag_name
(
'form'
)
.
submit
()
except
:
logger
.
exception
(
"Selenium could not submit create vm form"
)
raise
Exception
(
'Could not submit a form'
)
WebDriverWait
(
self
.
driver
,
self
.
conf
.
wait_max_sec
)
.
until
(
WebDriverWait
(
self
.
driver
,
self
.
conf
.
wait_max_sec
)
.
until
(
ec
.
visibility_of_element_located
((
ec
.
visibility_of_element_located
((
By
.
CLASS_NAME
,
'alert-success'
)))
By
.
CLASS_NAME
,
'alert-success'
)))
...
@@ -498,7 +672,7 @@ class CircleSeleniumMixin(object):
...
@@ -498,7 +672,7 @@ class CircleSeleniumMixin(object):
self
.
driver
.
get
(
"
%(host)
s/dashboard/vm/
%(id)
s/op/destroy/"
%
{
self
.
driver
.
get
(
"
%(host)
s/dashboard/vm/
%(id)
s/op/destroy/"
%
{
'host'
:
self
.
conf
.
host
,
'host'
:
self
.
conf
.
host
,
'id'
:
pk
})
'id'
:
pk
})
self
.
wait_and_accept_operation
()
self
.
wait_and_accept_operation
(
try_wait
=
"a[href*='/op/recover/']"
)
try
:
try
:
status_span
=
WebDriverWait
(
status_span
=
WebDriverWait
(
self
.
driver
,
self
.
conf
.
wait_max_sec
)
.
until
(
self
.
driver
,
self
.
conf
.
wait_max_sec
)
.
until
(
...
@@ -528,101 +702,3 @@ class CircleSeleniumMixin(object):
...
@@ -528,101 +702,3 @@ class CircleSeleniumMixin(object):
except
:
except
:
logger
.
exception
(
"Selenium can not destroy a VM"
)
logger
.
exception
(
"Selenium can not destroy a VM"
)
raise
Exception
(
"Can not destroy a VM"
)
raise
Exception
(
"Can not destroy a VM"
)
class
SeleniumMixin
(
object
):
def
list_options
(
self
,
select
):
try
:
option_dic
=
{}
select
=
Select
(
select
)
for
option
in
select
.
options
:
key
=
option
.
get_attribute
(
'value'
)
if
key
is
not
None
and
key
:
option_dic
[
key
]
=
[
option
.
text
]
return
option_dic
except
:
logger
.
exception
(
"Selenium cannot list the"
" select possibilities"
)
raise
Exception
(
'Cannot list the select possibilities'
)
def
select_option
(
self
,
select
,
what
=
None
):
"""
From an HTML select imput type try to choose the specified one.
Select is a selenium web element type. What represent both the
text of the option and it's ID.
"""
try
:
my_choice
=
None
options
=
self
.
list_options
(
select
)
select
=
Select
(
select
)
if
what
is
not
None
:
for
key
,
value
in
options
.
iteritems
():
if
what
in
key
:
my_choice
=
key
else
:
if
isinstance
(
value
,
list
):
for
single_value
in
value
:
if
what
in
single_value
:
my_choice
=
key
else
:
if
what
in
value
:
my_choice
=
key
if
my_choice
is
None
:
my_choose_list
=
options
.
keys
()
my_choice
=
my_choose_list
[
random
.
randint
(
0
,
len
(
my_choose_list
)
-
1
)]
select
.
select_by_value
(
my_choice
)
except
:
logger
.
exception
(
"Selenium cannot select the chosen one"
)
raise
Exception
(
'Cannot select the chosen one'
)
def
get_link_by_href
(
self
,
target_href
,
attributes
=
None
):
try
:
links
=
self
.
driver
.
find_elements_by_tag_name
(
'a'
)
for
link
in
links
:
href
=
link
.
get_attribute
(
'href'
)
if
href
is
not
None
and
href
:
if
target_href
in
href
:
perfect_fit
=
True
if
isinstance
(
attributes
,
dict
):
for
key
,
target_value
in
attributes
.
iteritems
():
attr_check
=
link
.
get_attribute
(
key
)
if
attr_check
is
not
None
and
attr_check
:
if
target_value
not
in
attr_check
:
perfect_fit
=
False
if
perfect_fit
:
return
link
except
:
logger
.
exception
(
"Selenium cannot find the href=
%
s link"
%
target_href
)
raise
Exception
(
'Cannot find the requested href'
)
def
click_on_link
(
self
,
link
):
"""
There are situations when selenium built in click() function
doesn't work as intended, that's when this function is used.
Fires a click event via javascript injection.
"""
try
:
# Javascript function to simulate a click on a link
javascript
=
(
"var link = arguments[0];"
"var cancelled = false;"
"if(document.createEvent) {"
" var event = document.createEvent(
\"
MouseEvents
\"
);"
" event.initMouseEvent("
"
\"
click
\"
, true, true, window, 0, 0, 0, 0, 0,"
" false,false,false,false,0,null);"
" cancelled = !link.dispatchEvent(event);"
"} else if(link.fireEvent) {"
" cancelled = !link.fireEvent(
\"
onclick
\"
);"
"} if (!cancelled) {"
" window.location = link.href;"
"}"
)
self
.
driver
.
execute_script
(
javascript
,
link
)
except
:
logger
.
exception
(
"Selenium cannot inject javascript to the page"
)
raise
Exception
(
'Cannot inject javascript to the page'
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment