Refactoring

This commit is contained in:
Michael Mintz 2023-02-08 22:37:26 -05:00
parent 5fd88b1fd8
commit e6d35b478e
5 changed files with 103 additions and 67 deletions

View File

@ -1,6 +1,7 @@
""" SeleniumBase Exceptions
NoSuchFileException => Called when self.assert_downloaded_file(...) fails.
NotUsingChromeException => Used by Chrome-only methods if not using Chrome.
NotUsingChromiumException => Used by Chromium-only methods if not Chromium.
OutOfScopeException => Used by BaseCase methods when setUp() is skipped.
TextNotVisibleException => Called when expected text fails to appear.
TimeLimitExceededException => Called when exceeding "--time-limit=SECONDS".
@ -16,6 +17,10 @@ class NotUsingChromeException(Exception):
pass
class NotUsingChromiumException(Exception):
pass
class OutOfScopeException(Exception):
pass

View File

@ -9,6 +9,7 @@ import sys
import time
import urllib3
import warnings
import zipfile
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.edge.service import Service as EdgeService
@ -351,8 +352,6 @@ def _unzip_to_new_folder(zip_file, folder):
proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK)
with proxy_dir_lock:
if not os.path.exists(folder):
import zipfile
zip_ref = zipfile.ZipFile(zip_file, "r")
os.makedirs(folder)
zip_ref.extractall(folder)

View File

@ -61,6 +61,12 @@ from selenium.webdriver.remote.remote_connection import LOGGER
from seleniumbase import config as sb_config
from seleniumbase.__version__ import __version__
from seleniumbase.common import decorators
from seleniumbase.common.exceptions import (
NotUsingChromeException,
NotUsingChromiumException,
OutOfScopeException,
VisualException,
)
from seleniumbase.config import settings
from seleniumbase.core import download_helper
from seleniumbase.core import log_helper
@ -6493,6 +6499,12 @@ class BaseCase(unittest.TestCase):
and self.headless
):
return os.path.join(os.path.expanduser("~"), "downloads")
elif (
self.driver.capabilities["browserName"].lower() == "chrome"
and int(self.get_chromedriver_version().split(".")[0]) >= 110
and self.headless
):
return os.path.abspath(".")
else:
return download_helper.get_downloads_folder()
return os.path.join(os.path.expanduser("~"), "downloads")
@ -7092,17 +7104,27 @@ class BaseCase(unittest.TestCase):
if browser_name.lower() == "chrome":
chrome = True
if not chrome:
from seleniumbase.common.exceptions import NotUsingChromeException
message = (
'Error: "%s" should only be called '
'by tests running with self.browser == "chrome"! '
'Error: "%s" should only be called by tests '
'running with "--browser=chrome" / "--chrome"! '
'You should add an "if" statement to your code before calling '
"this method if using browsers that are Not Chrome! "
'The browser detected was: "%s".' % (method, browser_name)
)
raise NotUsingChromeException(message)
def __fail_if_not_using_chromium(self, method):
browser_name = self.driver.capabilities["browserName"]
if not self.is_chromium():
message = (
'Error: "%s" should only be called by tests '
'running with a Chromium browser! (Chrome or Edge) '
'You should add an "if" statement to your code before calling '
"this method if using browsers that are Not Chromium! "
'The browser detected was: "%s".' % (method, browser_name)
)
raise NotUsingChromiumException(message)
def get_chrome_version(self):
self.__check_scope()
self.__fail_if_not_using_chrome("get_chrome_version()")
@ -7113,6 +7135,11 @@ class BaseCase(unittest.TestCase):
chrome_version = driver_capabilities["browserVersion"]
return chrome_version
def get_chromium_version(self):
self.__check_scope()
self.__fail_if_not_using_chromium("get_chromium_version()")
return self.__get_major_browser_version()
def get_chromedriver_version(self):
self.__check_scope()
self.__fail_if_not_using_chrome("get_chromedriver_version()")
@ -7121,14 +7148,19 @@ class BaseCase(unittest.TestCase):
chromedriver_version = chromedriver_version.split(" ")[0]
return chromedriver_version
def is_chromedriver_too_old(self):
"""Before chromedriver 73, there was no version check, which
means it's possible to run a new Chrome with old drivers."""
def get_chromium_driver_version(self):
self.__check_scope()
self.__fail_if_not_using_chrome("is_chromedriver_too_old()")
if int(self.get_chromedriver_version().split(".")[0]) < 73:
return True # chromedriver is too old! Please upgrade!
return False
self.__fail_if_not_using_chromium("get_chromium_version()")
driver_version = None
if "chrome" in self.driver.capabilities:
chrome_dict = self.driver.capabilities["chrome"]
driver_version = chrome_dict["chromedriverVersion"]
driver_version = driver_version.split(" ")[0]
elif "msedge" in self.driver.capabilities:
edge_dict = self.driver.capabilities["msedge"]
driver_version = edge_dict["msedgedriverVersion"]
driver_version = driver_version.split(" ")[0]
return driver_version
def get_mfa_code(self, totp_key=None):
"""Same as get_totp_code() and get_google_auth_password().
@ -9307,8 +9339,6 @@ class BaseCase(unittest.TestCase):
elif line.strip().startswith("*"):
minified_exception += line + "\n"
if minified_exception:
from seleniumbase.common.exceptions import VisualException
raise VisualException(minified_exception)
def _process_visual_baseline_logs(self):
@ -9688,8 +9718,6 @@ class BaseCase(unittest.TestCase):
if hasattr(self, "browser"): # self.browser stores the type of browser
return # All good: setUp() already initialized variables in "self"
else:
from seleniumbase.common.exceptions import OutOfScopeException
message = (
"\n It looks like you are trying to call a SeleniumBase method"
"\n from outside the scope of your test class's `self` object,"
@ -12734,9 +12762,18 @@ class BaseCase(unittest.TestCase):
############
@decorators.deprecated("The Driver Manager prevents old drivers.")
def is_chromedriver_too_old(self):
"""Before chromedriver 73, there was no version check, which
means it's possible to run a new Chrome with old drivers."""
self.__fail_if_not_using_chrome("is_chromedriver_too_old()")
if int(self.get_chromedriver_version().split(".")[0]) < 73:
return True # chromedriver is too old! Please upgrade!
return False
@decorators.deprecated("You should use re.escape() instead.")
def jq_format(self, code):
# DEPRECATED - re.escape() already performs the intended action.
# DEPRECATED - re.escape() already performs this action.
return js_utils._jq_format(code)
############

View File

@ -24,9 +24,7 @@ def get_domain_url(url):
def is_xpath_selector(selector):
"""
A basic method to determine if a selector is an xpath selector.
"""
"""Determine if a selector is an xpath selector."""
if (
selector.startswith("/")
or selector.startswith("./")
@ -37,9 +35,7 @@ def is_xpath_selector(selector):
def is_link_text_selector(selector):
"""
A basic method to determine if a selector is a link text selector.
"""
"""Determine if a selector is a link text selector."""
if (
selector.startswith("link=")
or selector.startswith("link_text=")
@ -50,9 +46,7 @@ def is_link_text_selector(selector):
def is_partial_link_text_selector(selector):
"""
A basic method to determine if a selector is a partial link text selector.
"""
"""Determine if a selector is a partial link text selector."""
if (
selector.startswith("partial_link=")
or selector.startswith("partial_link_text=")
@ -66,18 +60,14 @@ def is_partial_link_text_selector(selector):
def is_name_selector(selector):
"""
A basic method to determine if a selector is a name selector.
"""
"""Determine if a selector is a name selector."""
if selector.startswith("name=") or selector.startswith("&"):
return True
return False
def get_link_text_from_selector(selector):
"""
A basic method to get the link text from a link text selector.
"""
"""Get the link text from a link text selector."""
if selector.startswith("link="):
return selector[len("link="):]
elif selector.startswith("link_text="):
@ -88,9 +78,7 @@ def get_link_text_from_selector(selector):
def get_partial_link_text_from_selector(selector):
"""
A basic method to get the partial link text from a partial link selector.
"""
"""Get the partial link text from a partial link selector."""
if selector.startswith("partial_link="):
return selector[len("partial_link="):]
elif selector.startswith("partial_link_text="):
@ -107,9 +95,7 @@ def get_partial_link_text_from_selector(selector):
def get_name_from_selector(selector):
"""
A basic method to get the name from a name selector.
"""
"""Get the name from a name selector."""
if selector.startswith("name="):
return selector[len("name="):]
if selector.startswith("&"):
@ -143,8 +129,7 @@ def is_valid_url(url):
def _get_unique_links(page_url, soup):
"""
Returns all unique links.
"""Returns all unique links.
Includes:
"a"->"href", "img"->"src", "link"->"href", and "script"->"src" links.
"""
@ -225,8 +210,7 @@ def _get_link_status_code(
If the timeout is exceeded, will return a 404.
If "verify" is False, will ignore certificate errors.
For a list of available status codes, see:
https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
"""
https://en.wikipedia.org/wiki/List_of_HTTP_status_codes """
status_code = None
try:
response = requests.head(
@ -246,8 +230,7 @@ def _print_unique_links_with_status_codes(page_url, soup):
and then prints out those links with their status codes.
Format: ["link" -> "status_code"] (per line)
Page links include those obtained from:
"a"->"href", "img"->"src", "link"->"href", and "script"->"src".
"""
"a"->"href", "img"->"src", "link"->"href", and "script"->"src". """
links = _get_unique_links(page_url, soup)
for link in links:
status_code = _get_link_status_code(link)

View File

@ -7,6 +7,7 @@ import re
import string
import sys
import time
import zipfile
logger = logging.getLogger(__name__)
IS_POSIX = sys.platform.startswith(("darwin", "cygwin", "linux"))
@ -137,8 +138,6 @@ class Patcher(object):
"""
:return: path to unpacked executable
"""
import zipfile
logger.debug("unzipping %s" % fp)
try:
os.unlink(self.zip_path)
@ -147,12 +146,19 @@ class Patcher(object):
os.makedirs(self.zip_path, mode=0o755, exist_ok=True)
with zipfile.ZipFile(fp, mode="r") as zf:
zf.extract(self.exe_name, self.zip_path)
os.rename(
os.path.join(self.zip_path, self.exe_name), self.executable_path
)
os.remove(fp)
os.rmdir(self.zip_path)
os.chmod(self.executable_path, 0o755)
try:
os.rename(
os.path.join(self.zip_path, self.exe_name),
self.executable_path,
)
os.remove(fp)
except PermissionError:
pass
try:
os.rmdir(self.zip_path)
os.chmod(self.executable_path, 0o755)
except PermissionError:
pass
return self.executable_path
@staticmethod
@ -170,8 +176,6 @@ class Patcher(object):
@staticmethod
def gen_random_cdc():
import string
cdc = random.choices(string.ascii_lowercase, k=26)
cdc[-6:-4] = map(str.upper, cdc[-6:-4])
cdc[2] = cdc[0]
@ -220,32 +224,40 @@ class Patcher(object):
fh.write(file_bin)
return True
def is_binary_patched_new(self, executable_path=None):
executable_path = executable_path or self.executable_path
with io.open(executable_path, "rb") as fh:
return fh.read().find(b"undetected chromedriver") != -1
@staticmethod
def gen_random_cdc_beta():
cdc = random.choices(string.ascii_letters, k=27)
return "".join(cdc).encode()
def patch_exe_new(self):
logger.info("patching driver executable %s" % self.executable_path)
def is_binary_patched_beta(self, executable_path=None):
executable_path = executable_path or self.executable_path
try:
with io.open(executable_path, "rb") as fh:
return fh.read().find(b"undetected chromedriver") != -1
except FileNotFoundError:
return False
def patch_exe_beta(self):
with io.open(self.executable_path, "r+b") as fh:
content = fh.read()
match_injected_codeblock = re.search(rb"{window.*;}", content)
match_injected_codeblock = re.search(
rb"\{window\.cdc.*?;\}", content
)
target_bytes = None
if match_injected_codeblock:
target_bytes = match_injected_codeblock[0]
new_target_bytes = (
b'{console.log("undetected chromedriver 1337!")}'.ljust(
b'{console.log("chromedriver is undetectable!")}'.ljust(
len(target_bytes), b" "
)
)
if target_bytes:
new_content = content.replace(target_bytes, new_target_bytes)
if new_content == content:
pass # Failure to patch driver
pass # Unable to patch driver
else:
# Patch now
fh.seek(0)
fh.write(new_content)
else:
pass # Already patched
def __repr__(self):
return "{0:s}({1:s})".format(