Commit 89292b7d by Csók Tamás

selenium: logging revisited, errors corrected

parent 8ff74260
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
import logging import logging
from sys import stdout
import random import random
import re import re
import urlparse import urlparse
...@@ -34,10 +33,14 @@ from vm.models import Instance ...@@ -34,10 +33,14 @@ from vm.models import Instance
from .config import SeleniumConfig from .config import SeleniumConfig
from .util import CircleSeleniumMixin, SeleniumMixin from .util import CircleSeleniumMixin, SeleniumMixin
logger = logging.getLogger(__name__)
consoleHandler = logging.StreamHandler(stdout)
logger.addHandler(consoleHandler)
conf = SeleniumConfig() conf = SeleniumConfig()
log_formatter = logging.Formatter(conf.log_format)
logger = logging.getLogger(conf.logger_name)
fileHandler = logging.handlers.RotatingFileHandler(
conf.log_file, maxBytes=conf.log_size, backupCount=conf.log_backup)
fileHandler.setFormatter(log_formatter)
fileHandler.setLevel(logging.WARNING)
logger.addHandler(fileHandler)
class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin): class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin):
...@@ -82,9 +85,9 @@ class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin): ...@@ -82,9 +85,9 @@ class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin):
elif len(template_pool) == 1: elif len(template_pool) == 1:
chosen = template_pool[0] chosen = template_pool[0]
else: else:
logging.warning("Selenium did not found any templates") logger.exception("Selenium did not found any templates")
logging.exception( raise Exception(
"System did not meet required conditions to continue") "Selenium did not found any templates")
self.driver.get('%s/dashboard/template/%s/' % (conf.host, chosen)) self.driver.get('%s/dashboard/template/%s/' % (conf.host, chosen))
acces_form = self.driver.find_element_by_css_selector( acces_form = self.driver.find_element_by_css_selector(
"form[action*='/dashboard/template/%(template_id)s/acl/']" "form[action*='/dashboard/template/%(template_id)s/acl/']"
...@@ -106,10 +109,10 @@ class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin): ...@@ -106,10 +109,10 @@ class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin):
user_text = re.split(r':[ ]?', user.text) user_text = re.split(r':[ ]?', user.text)
if len(user_text) == 2: if len(user_text) == 2:
found_name = re.search(r'[\w\W]+(?=\))', user_text[1]).group() found_name = re.search(r'[\w\W]+(?=\))', user_text[1]).group()
logging.info("'%(user)s' found in ACL " logger.warning("'%(user)s' found in ACL "
"list for template %(id)s" % { "list for template %(id)s" % {
'user': found_name, 'user': found_name,
'id': chosen}) 'id': chosen})
found_users.append(found_name) found_users.append(found_name)
self.assertIn(conf.client_name, found_users, self.assertIn(conf.client_name, found_users,
"Could not add user to template's ACL") "Could not add user to template's ACL")
...@@ -124,7 +127,7 @@ class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin): ...@@ -124,7 +127,7 @@ class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin):
By.ID, 'confirmation-modal'))) By.ID, 'confirmation-modal')))
template_list = self.driver.find_elements_by_class_name( template_list = self.driver.find_elements_by_class_name(
'template-choose-list-element') 'template-choose-list-element')
logging.info('Selenium found %(count)s template possibilities' % { logger.warning('Selenium found %(count)s template possibilities' % {
'count': len(template_list)}) 'count': len(template_list)})
(self.assertIsNotNone( (self.assertIsNotNone(
template_list, "Selenium can not find the create template list") or template_list, "Selenium can not find the create template list") or
...@@ -157,7 +160,7 @@ class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin): ...@@ -157,7 +160,7 @@ class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin):
success = False success = False
self.login() self.login()
for template_id in self.template_ids: for template_id in self.template_ids:
logging.info("Deleting template %s" % template_id) logger.warning("Deleting template %s" % template_id)
self.delete_template(template_id) self.delete_template(template_id)
existing_templates = self.get_template_id() existing_templates = self.get_template_id()
if len(existing_templates) == 0: if len(existing_templates) == 0:
...@@ -181,9 +184,9 @@ class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin): ...@@ -181,9 +184,9 @@ class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin):
By.ID, 'confirmation-modal'))) By.ID, 'confirmation-modal')))
vm_list = self.driver.find_elements_by_class_name( vm_list = self.driver.find_elements_by_class_name(
'vm-create-template-summary') 'vm-create-template-summary')
logging.info("Selenium found %(vm_number)s virtual machine template " logger.warning("Selenium found %(vm_number)s virtual machine template "
" possibilities" % { " possibilities" % {
'vm_number': len(vm_list)}) 'vm_number': len(vm_list)})
(self.assertIsNotNone( (self.assertIsNotNone(
vm_list, "Selenium can not find the VM list") or vm_list, "Selenium can not find the VM list") or
self.assertGreater(len(vm_list), 0, "The create VM list is empty")) self.assertGreater(len(vm_list), 0, "The create VM list is empty"))
...@@ -200,8 +203,8 @@ class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin): ...@@ -200,8 +203,8 @@ class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin):
"none", "", "none", "",
"block", "none"] "block", "none"]
states = self.view_change("vm") states = self.view_change("vm")
logging.info('states: [%s]' % ', '.join(map(str, states))) logger.warning('states: [%s]' % ', '.join(map(str, states)))
logging.info('expected: [%s]' % ', '.join(map(str, expected_states))) logger.warning('expected: [%s]' % ', '.join(map(str, expected_states)))
self.assertListEqual(states, expected_states, self.assertListEqual(states, expected_states,
"The view mode does not change for VM listing") "The view mode does not change for VM listing")
...@@ -211,8 +214,8 @@ class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin): ...@@ -211,8 +214,8 @@ class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin):
"none", "", "none", "",
"block", "none"] "block", "none"]
states = self.view_change("node") states = self.view_change("node")
logging.info('states: [%s]' % ', '.join(map(str, states))) logger.warning('states: [%s]' % ', '.join(map(str, states)))
logging.info('expected: [%s]' % ', '.join(map(str, expected_states))) logger.warning('expected: [%s]' % ', '.join(map(str, expected_states)))
self.assertListEqual(states, expected_states, self.assertListEqual(states, expected_states,
"The view mode does not change for NODE listing") "The view mode does not change for NODE listing")
......
...@@ -24,6 +24,16 @@ class SeleniumConfig: ...@@ -24,6 +24,16 @@ class SeleniumConfig:
wait_max_sec = 10 wait_max_sec = 10
# How much sec can pass before the activity is no longer happened recently # How much sec can pass before the activity is no longer happened recently
recently_sec = 90 recently_sec = 90
# Name of the logger (necessary to override test logger)
logger_name = "selenium"
# File where the log should be stored
log_file = "selenium.log"
# Log file max size in Bytes
log_size = 1024 * 100
# Format of the log file
log_format = "%(asctime)s: %(name)s: %(levelname)s: %(message)s"
# Backup count of the logfiles
log_backup = 5
# Accented letters from which selenium can choose to name stuff # Accented letters from which selenium can choose to name stuff
accents = u"áéíöóúűÁÉÍÖÓÜÚŰ" accents = u"áéíöóúűÁÉÍÖÓÜÚŰ"
......
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from datetime import datetime from datetime import datetime
import logging import logging
from sys import stdout
import random import random
import re import re
import time import time
...@@ -30,9 +29,9 @@ from selenium.webdriver.support import expected_conditions as ec ...@@ -30,9 +29,9 @@ from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.select import Select from selenium.webdriver.support.select import Select
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
logger = logging.getLogger(__name__) from .config import SeleniumConfig
consoleHandler = logging.StreamHandler(stdout)
logger.addHandler(consoleHandler) logger = logging.getLogger(SeleniumConfig.logger_name)
class CircleSeleniumMixin(object): class CircleSeleniumMixin(object):
...@@ -73,7 +72,8 @@ class CircleSeleniumMixin(object): ...@@ -73,7 +72,8 @@ class CircleSeleniumMixin(object):
except: except:
time.sleep(0.5) time.sleep(0.5)
except: except:
logging.exception('Selenium cannot find the form controls') logger.exception("Selenium 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):
""" """
...@@ -97,8 +97,10 @@ class CircleSeleniumMixin(object): ...@@ -97,8 +97,10 @@ class CircleSeleniumMixin(object):
form.send_keys(argument) form.send_keys(argument)
accept.click() accept.click()
except: except:
logging.exception( logger.exception("Selenium cannot accept the"
'Selenium cannot accept the operation confirmation') " operation confirmation")
raise Exception(
'Cannot accept the operation confirmation')
def save_template_from_vm(self, name): def save_template_from_vm(self, name):
try: try:
...@@ -111,9 +113,9 @@ class CircleSeleniumMixin(object): ...@@ -111,9 +113,9 @@ class CircleSeleniumMixin(object):
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(recent_deploy): if not self.check_operation_result(recent_deploy):
logging.warning("Selenium cannot deploy the " logger.warning("Selenium cannot deploy the "
"chosen template virtual machine") "chosen template virtual machine")
logging.exception('Cannot deploy the virtual machine') raise Exception('Cannot deploy the virtual machine')
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((
...@@ -123,9 +125,9 @@ class CircleSeleniumMixin(object): ...@@ -123,9 +125,9 @@ class CircleSeleniumMixin(object):
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(recent_shut_off): if not self.check_operation_result(recent_shut_off):
logging.warning("Selenium cannot shut off the " logger.warning("Selenium cannot shut off the "
"chosen template virtual machine") "chosen template virtual machine")
logging.exception('Cannot shut off the virtual machine') raise Exception('Cannot shut off the virtual machine')
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((
...@@ -134,8 +136,9 @@ class CircleSeleniumMixin(object): ...@@ -134,8 +136,9 @@ class CircleSeleniumMixin(object):
self.wait_and_accept_operation(name) self.wait_and_accept_operation(name)
return name return name
except: except:
logging.exception( logger.exception("Selenium cannot save a vm as a template")
'Selenium cannot save a vm as a template') raise Exception(
'Cannot save a vm as a template')
def create_base_template(self, name=None, architecture="x86-64", def create_base_template(self, name=None, architecture="x86-64",
method=None, op_system=None, lease=None, method=None, op_system=None, lease=None,
...@@ -171,8 +174,10 @@ class CircleSeleniumMixin(object): ...@@ -171,8 +174,10 @@ class CircleSeleniumMixin(object):
"input.btn[type='submit']").click() "input.btn[type='submit']").click()
return self.save_template_from_vm(name) return self.save_template_from_vm(name)
except: except:
logging.exception( logger.exception("Selenium cannot create a base"
'Selenium cannot create a base template virtual machine') " template virtual machine")
raise Exception(
'Cannot create a base template virtual machine')
def get_template_id(self, name=None, from_all=False): def get_template_id(self, name=None, from_all=False):
""" """
...@@ -195,7 +200,8 @@ class CircleSeleniumMixin(object): ...@@ -195,7 +200,8 @@ class CircleSeleniumMixin(object):
ec.presence_of_element_located(( ec.presence_of_element_located((
By.CSS_SELECTOR, css_selector_of_a_template))) By.CSS_SELECTOR, css_selector_of_a_template)))
except: except:
logging.warning("Selenium could not locate any templates") logger.warning("Selenium could not locate any templates")
raise Exception("Could not locate any templates")
template_table = self.driver.find_element_by_css_selector( template_table = self.driver.find_element_by_css_selector(
"table[class*='template-list-table']") "table[class*='template-list-table']")
templates = template_table.find_elements_by_css_selector("td.name") templates = template_table.find_elements_by_css_selector("td.name")
...@@ -209,22 +215,24 @@ class CircleSeleniumMixin(object): ...@@ -209,22 +215,24 @@ class CircleSeleniumMixin(object):
r'\d+', r'\d+',
template_link.get_attribute("outerHTML")).group() template_link.get_attribute("outerHTML")).group()
found_template_ids.append(template_id) found_template_ids.append(template_id)
logging.info("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.text,
'id': template_id}) 'id': template_id})
except NoSuchElementException: except NoSuchElementException:
pass pass
except: except:
raise raise
if not found_template_ids and name is not None: if not found_template_ids and name is not None:
logging.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" % {
'name': name}) 'name': name})
raise Exception("Could not find the specified template")
return found_template_ids return found_template_ids
except: except:
logging.exception( logger.exception('Selenium cannot find the template\'s id')
'Selenium cannot found the template\'s id') raise Exception(
'Cannot find the template\'s id')
def check_operation_result(self, operation_id, restore=True): def check_operation_result(self, operation_id, restore=True):
""" """
...@@ -246,7 +254,7 @@ class CircleSeleniumMixin(object): ...@@ -246,7 +254,7 @@ class CircleSeleniumMixin(object):
result = WebDriverWait(self.driver, self.conf.wait_max_sec).until( result = WebDriverWait(self.driver, self.conf.wait_max_sec).until(
ec.visibility_of_element_located(( ec.visibility_of_element_located((
By.ID, "activity_status"))) By.ID, "activity_status")))
logging.info("%(id)s result text is '%(result)s'" % { logger.warning("%(id)s result text is '%(result)s'" % {
'id': operation_id, 'id': operation_id,
'result': result.text}) 'result': result.text})
if (result.text == "success"): if (result.text == "success"):
...@@ -257,12 +265,14 @@ class CircleSeleniumMixin(object): ...@@ -257,12 +265,14 @@ class CircleSeleniumMixin(object):
else: else:
out = False out = False
if restore: if restore:
logging.info("Restoring to %s url" % url_save) logger.warning("Restoring to %s url" % url_save)
self.driver.get(url_save) self.driver.get(url_save)
return out return out
except: except:
logging.exception( logger.exception("Selenium cannot check the"
'Selenium cannot check the result of an operation') " result of an operation")
raise Exception(
'Cannot check the result of an operation')
def recently(self, timeline_dict, second=None): def recently(self, timeline_dict, second=None):
if second is None: if second is None:
...@@ -275,8 +285,10 @@ class CircleSeleniumMixin(object): ...@@ -275,8 +285,10 @@ class CircleSeleniumMixin(object):
if delta.total_seconds() <= second: if delta.total_seconds() <= second:
return value return value
except: except:
logging.exception( logger.exception("Selenium cannot filter"
'Selenium cannot filter timeline activities to recent') " timeline activities to recent")
raise Exception(
'Cannot filter timeline activities to recent')
def get_timeline_elements(self, code=None): def get_timeline_elements(self, code=None):
try: try:
...@@ -297,19 +309,20 @@ class CircleSeleniumMixin(object): ...@@ -297,19 +309,20 @@ class CircleSeleniumMixin(object):
By.ID, "activity-timeline"))) By.ID, "activity-timeline")))
searched_activity = timeline.find_elements_by_css_selector( searched_activity = timeline.find_elements_by_css_selector(
css_activity_selector) css_activity_selector)
logging.info("Found activity list for %s:" % code) logger.warning("Found activity list for %s:" % code)
for activity in searched_activity: for activity in searched_activity:
activity_id = activity.get_attribute('data-activity-id') activity_id = activity.get_attribute('data-activity-id')
activity_text = activity.text activity_text = activity.text
key = re.search( key = re.search(
r'\d+-\d+-\d+ \d+:\d+,', activity_text).group()[:-1] r'\d+-\d+-\d+ \d+:\d+,', activity_text).group()[:-1]
logging.info("%(id)s @ %(activity)s" % { logger.warning("%(id)s @ %(activity)s" % {
'id': activity_id, 'id': activity_id,
'activity': key}) 'activity': key})
activity_dict[key] = activity_id activity_dict[key] = activity_id
return activity_dict return activity_dict
except: except:
logging.exception('Selenium cannot find the searched activity') logger.exception('Selenium cannot find the searched activity')
raise Exception('Cannot find the searched activity')
def create_template_from_base(self, delete_disk=True, name=None): def create_template_from_base(self, delete_disk=True, name=None):
try: try:
...@@ -346,13 +359,15 @@ class CircleSeleniumMixin(object): ...@@ -346,13 +359,15 @@ class CircleSeleniumMixin(object):
self.get_timeline_elements( self.get_timeline_elements(
"vm.Instance.remove_disk")) "vm.Instance.remove_disk"))
if not self.check_operation_result(recent_remove_disk): if not self.check_operation_result(recent_remove_disk):
logging.warning("Selenium cannot delete disk " logger.warning("Selenium cannot delete disk "
"of the chosen template") "of the chosen template")
logging.exception('Cannot delete disk') raise Exception('Cannot delete disk')
return self.save_template_from_vm(name) return self.save_template_from_vm(name)
except: except:
logging.exception( logger.exception("Selenium cannot start a"
'Selenium cannot start a template from a base one') " template from a base one")
raise Exception(
'Cannot start a template from a base one')
def delete_template(self, template_id): def delete_template(self, template_id):
try: try:
...@@ -368,10 +383,12 @@ class CircleSeleniumMixin(object): ...@@ -368,10 +383,12 @@ class CircleSeleniumMixin(object):
By.CLASS_NAME, 'alert-success'))) By.CLASS_NAME, 'alert-success')))
url = urlparse.urlparse(self.driver.current_url) url = urlparse.urlparse(self.driver.current_url)
if "/template/list/" not in url.path: if "/template/list/" not in url.path:
logging.exception( logger.warning('CIRCLE does not redirect to /template/list/')
raise Exception(
'System does not redirect to template listing') 'System does not redirect to template listing')
except: except:
logging.exception('Selenium cannot delete the desired template') logger.exception("Selenium cannot delete the desired template")
raise Exception('Cannot delete the desired template')
def create_random_vm(self): def create_random_vm(self):
try: try:
...@@ -392,7 +409,8 @@ class CircleSeleniumMixin(object): ...@@ -392,7 +409,8 @@ class CircleSeleniumMixin(object):
pk = re.search(r'\d+', url.path).group() pk = re.search(r'\d+', url.path).group()
return pk return pk
except: except:
logging.exception('Selenium cannot start a VM') logger.exception("Selenium cannot start a VM")
raise Exception('Cannot start a VM')
def view_change(self, target_box): def view_change(self, target_box):
driver = self.driver driver = self.driver
...@@ -438,10 +456,10 @@ class CircleSeleniumMixin(object): ...@@ -438,10 +456,10 @@ class CircleSeleniumMixin(object):
recent_destroy_vm = self.recently( recent_destroy_vm = self.recently(
self.get_timeline_elements("vm.Instance.destroy")) self.get_timeline_elements("vm.Instance.destroy"))
if not self.check_operation_result(recent_destroy_vm): if not self.check_operation_result(recent_destroy_vm):
logging.warning("Selenium cannot destroy " logger.warning("Selenium cannot destroy "
"the chosen %(id)s vm" % { "the chosen %(id)s vm" % {
'id': pk}) 'id': pk})
logging.exception('Cannot destroy the specified vm') raise Exception('Cannot destroy the specified vm')
self.driver.get('%s/dashboard/vm/%s/' % (self.conf.host, pk)) self.driver.get('%s/dashboard/vm/%s/' % (self.conf.host, pk))
try: try:
WebDriverWait(self.driver, self.conf.wait_max_sec).until( WebDriverWait(self.driver, self.conf.wait_max_sec).until(
...@@ -452,7 +470,8 @@ class CircleSeleniumMixin(object): ...@@ -452,7 +470,8 @@ class CircleSeleniumMixin(object):
except: except:
return False return False
except: except:
logging.exception("Selenium can not destroy a VM") logger.exception("Selenium can not destroy a VM")
raise Exception("Can not destroy a VM")
class SeleniumMixin(object): class SeleniumMixin(object):
...@@ -466,8 +485,10 @@ class SeleniumMixin(object): ...@@ -466,8 +485,10 @@ class SeleniumMixin(object):
option_dic[key] = [option.text] option_dic[key] = [option.text]
return option_dic return option_dic
except: except:
logging.exception( logger.exception("Selenium cannot list the"
'Selenium cannot list the select possibilities') " select possibilities")
raise Exception(
'Cannot list the select possibilities')
def select_option(self, select, what=None): def select_option(self, select, what=None):
""" """
...@@ -497,8 +518,9 @@ class SeleniumMixin(object): ...@@ -497,8 +518,9 @@ class SeleniumMixin(object):
0, len(my_choose_list) - 1)] 0, len(my_choose_list) - 1)]
select.select_by_value(my_choice) select.select_by_value(my_choice)
except: except:
logging.exception( logger.exception("Selenium cannot select the chosen one")
'Selenium cannot select the chosen one') raise Exception(
'Cannot select the chosen one')
def get_link_by_href(self, target_href, attributes=None): def get_link_by_href(self, target_href, attributes=None):
try: try:
...@@ -517,8 +539,9 @@ class SeleniumMixin(object): ...@@ -517,8 +539,9 @@ class SeleniumMixin(object):
if perfect_fit: if perfect_fit:
return link return link
except: except:
logging.exception( logger.exception(
'Selenium cannot find the href=%s link' % target_href) "Selenium cannot find the href=%s link" % target_href)
raise Exception('Cannot find the requested href')
def click_on_link(self, link): def click_on_link(self, link):
""" """
...@@ -544,5 +567,6 @@ class SeleniumMixin(object): ...@@ -544,5 +567,6 @@ class SeleniumMixin(object):
"}") "}")
self.driver.execute_script(javascript, link) self.driver.execute_script(javascript, link)
except: except:
logging.exception( logger.exception("Selenium cannot inject javascript to the page")
'Selenium cannot inject javascript to the page') raise Exception(
'Cannot inject javascript to the page')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment