inital push
This commit is contained in:
parent
dc15d4956b
commit
2645506fc8
|
@ -0,0 +1,28 @@
|
||||||
|
# Selenium Automation Template
|
||||||
|
A template to get you setup to pass a command file and run commands.
|
||||||
|
|
||||||
|
# Note(s)
|
||||||
|
This is designed with using Python mixins to inherit functionality from discrete classes that have discrete methods. The only "global" variable should be in the Context class.
|
||||||
|
|
||||||
|
!Important! BUILD OUT THE LOGIC FIRST AS THIS IS JUST TO GET STARTED!
|
||||||
|
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
``` sudo apt install python3 python-pip libgirepository1.0-dev ```
|
||||||
|
|
||||||
|
|
||||||
|
# Setup
|
||||||
|
|
||||||
|
*** Change directory to the src.
|
||||||
|
|
||||||
|
``` python3 -m venv ./venv ```
|
||||||
|
|
||||||
|
``` source venv/bin/activate ```
|
||||||
|
|
||||||
|
``` pip install -r requirements.txt ```
|
||||||
|
|
||||||
|
``` python . <your command file> ```
|
||||||
|
|
||||||
|
... when done ...
|
||||||
|
|
||||||
|
``` deactivate ```
|
|
@ -0,0 +1,5 @@
|
||||||
|
beautifulsoup4==4.9.0
|
||||||
|
pkg-resources==0.0.0
|
||||||
|
selenium==3.141.0
|
||||||
|
soupsieve==2.0
|
||||||
|
urllib3==1.25.8
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
# Python imports
|
||||||
|
import sys, os, json
|
||||||
|
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from core import Context
|
||||||
|
|
||||||
|
|
||||||
|
class Main(Context):
|
||||||
|
def __init__(self, args):
|
||||||
|
super().__init__(args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
with open(args.file) as f:
|
||||||
|
self.logger.debug("Opened command file...")
|
||||||
|
# Fill out your logic for parsing a command file...
|
||||||
|
# Then call the "call_method" methid to run a command against that logic.
|
||||||
|
pass
|
||||||
|
|
||||||
|
if "true" in args.persist.lower():
|
||||||
|
input("Press 'Enter' key to close the browser...")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.debug(e, exec_info=True)
|
||||||
|
|
||||||
|
self.driver.quit()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def call_method(self, method_name, data = None):
|
||||||
|
mName = str(method_name)
|
||||||
|
method = getattr(self, mName, lambda data: "No valid key passed...\nkey= " + mName + "\nargs= " + data)
|
||||||
|
return method(data) if data else method()
|
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
# Python imports
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from __init__ import Main
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
# Add long and short arguments
|
||||||
|
parser.add_argument("--file", "-f", help="The instructions file to use if any.")
|
||||||
|
parser.add_argument("--browser", "-b", default="firefox", help="ie, chrome, firefox (Optional)")
|
||||||
|
parser.add_argument("--headless", "-hm", default=False, help="Run browser in headless mode.")
|
||||||
|
parser.add_argument("--persist", "-p", default=False, help="Keep browser open after run. (Optional)")
|
||||||
|
|
||||||
|
|
||||||
|
# Read arguments (If any...)
|
||||||
|
args = parser.parse_args()
|
||||||
|
main = Main(args)
|
||||||
|
except Exception as e:
|
||||||
|
print( repr(e) )
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Python imports
|
||||||
|
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from .utils import Logger, Browser
|
||||||
|
from .mixins import ControlerMixin
|
||||||
|
|
||||||
|
|
||||||
|
class Context(ControlerMixin):
|
||||||
|
"""
|
||||||
|
The Context class consumes mixins to add functionality as needed.
|
||||||
|
"""
|
||||||
|
def __init__(self, args):
|
||||||
|
"""
|
||||||
|
Construct a new 'Context' object which pulls in mixins.
|
||||||
|
:param args: The terminal passed arguments
|
||||||
|
|
||||||
|
:return: returns nothing
|
||||||
|
"""
|
||||||
|
self.logger = Logger().get_logger("MAIN")
|
||||||
|
browser = Browser()
|
||||||
|
self.driver = browser.get_browser(args.browser, args.headless) # The browser driver
|
||||||
|
self.url = "" # The url we are pointing to
|
|
@ -0,0 +1,5 @@
|
||||||
|
from .mixins import ControlerMixin
|
||||||
|
|
||||||
|
from .utils import Logger
|
||||||
|
from .utils import Browser
|
||||||
|
from .Context import Context
|
|
@ -0,0 +1,76 @@
|
||||||
|
# Python imports
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
class ControlerMixin:
|
||||||
|
"""
|
||||||
|
The ControlerMixin has methods to manage interaction with the browser.
|
||||||
|
These get called from the _init__.Main class and ran.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def getImage(self):
|
||||||
|
"""
|
||||||
|
Get image of page we are on.
|
||||||
|
|
||||||
|
:return: no return data
|
||||||
|
"""
|
||||||
|
folder = self.domain
|
||||||
|
if not os.path.exists(folder):
|
||||||
|
os.mkdir(folder)
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
name = folder + "_" + str(i) + ".png"
|
||||||
|
toFile = folder + "/" + fName
|
||||||
|
while os.path.exists(file):
|
||||||
|
i += 1
|
||||||
|
name = folder + "__" + i + ".png"
|
||||||
|
toFile = folder + "/" + fName
|
||||||
|
|
||||||
|
self.logger.debug("Screenshot File Path/Name: " + toFile)
|
||||||
|
self.driver.save_screenshot(toFile)
|
||||||
|
|
||||||
|
|
||||||
|
def createXPath(self, data):
|
||||||
|
"""
|
||||||
|
Don't call directly.
|
||||||
|
|
||||||
|
:return: created xpath string
|
||||||
|
"""
|
||||||
|
|
||||||
|
keys = data.keys()
|
||||||
|
attribList = []
|
||||||
|
xpathStr = ""
|
||||||
|
queryCount = 0
|
||||||
|
|
||||||
|
if "elm" in keys:
|
||||||
|
xpathStr = "//" + data["elm"] + "["
|
||||||
|
queryCount += 1
|
||||||
|
else:
|
||||||
|
xpathStr = "//" + data["elm"] + "["
|
||||||
|
|
||||||
|
if "id" in keys:
|
||||||
|
attribList.append("@id='" + data["id"] + "'")
|
||||||
|
queryCount += 1
|
||||||
|
if "class" in keys:
|
||||||
|
attribList.append("@class='" + data["class"] + "'")
|
||||||
|
queryCount += 1
|
||||||
|
if "type" in keys:
|
||||||
|
attribList.append("@type='" + data["type"] + "'")
|
||||||
|
queryCount += 1
|
||||||
|
if "value" in keys:
|
||||||
|
attribList.append("@value='" + data["value"] + "'")
|
||||||
|
queryCount += 1
|
||||||
|
|
||||||
|
if len(attribList) > 1:
|
||||||
|
xpathStr += " and ".join(attribList)
|
||||||
|
else:
|
||||||
|
xpathStr += attribList[0]
|
||||||
|
|
||||||
|
xpathStr += "]"
|
||||||
|
self.logger.debug("Generated XPath: " + xpathStr)
|
||||||
|
return xpathStr
|
|
@ -0,0 +1 @@
|
||||||
|
from .ControlerMixin import ControlerMixin
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.firefox.options import Options as FOptions
|
||||||
|
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
class Browser:
|
||||||
|
"""
|
||||||
|
The Browser allows us to bring in selenium driver (a.k.a browser) related objects
|
||||||
|
"""
|
||||||
|
def get_browser(self, browserType = None, headless = None):
|
||||||
|
"""
|
||||||
|
Construct new selenium driver (a.k.a browser object)
|
||||||
|
Sets the "self.driver" in Context object.
|
||||||
|
:note: Should consider creating methods per browser type.
|
||||||
|
:param browserType: The browser we want to use
|
||||||
|
:param headless: If we have a gui or not
|
||||||
|
"""
|
||||||
|
driver = None
|
||||||
|
_log_path = "./core/logs/webdriver.log"
|
||||||
|
|
||||||
|
if "firefox" in browserType:
|
||||||
|
_options = FOptions()
|
||||||
|
profile = webdriver.FirefoxProfile()
|
||||||
|
|
||||||
|
profile.accept_untrusted_certs = True
|
||||||
|
if headless:
|
||||||
|
_options.add_aregument("--headless")
|
||||||
|
|
||||||
|
driver = webdriver.Firefox(options=_options, firefox_profile=profile, log_path=_log_path)
|
||||||
|
|
||||||
|
|
||||||
|
return driver
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Python imports
|
||||||
|
import os, logging
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_logger(self, loggerName = "NO_LOGGER_NAME_PASSED", createFile = True):
|
||||||
|
"""
|
||||||
|
Create a new logging object and return it.
|
||||||
|
:note:
|
||||||
|
NOSET # Don't know the actual log level of this... (defaulting or litterally none?)
|
||||||
|
Log Levels (From least to most)
|
||||||
|
Type Value
|
||||||
|
CRITICAL 50
|
||||||
|
ERROR 40
|
||||||
|
WARNING 30
|
||||||
|
INFO 20
|
||||||
|
DEBUG 10
|
||||||
|
:param loggerName: Sets the name of the logger object. (Used in log lines)
|
||||||
|
:param createFile: Whether we create a log file or just pump to terminal
|
||||||
|
|
||||||
|
:return: returns the logging object we created
|
||||||
|
"""
|
||||||
|
|
||||||
|
globalLogLvl = logging.DEBUG # Keep this at highest so that handlers can filter to their desired levels
|
||||||
|
chLogLevel = logging.CRITICAL # Prety musch the only one we change ever
|
||||||
|
fhLogLevel = logging.DEBUG
|
||||||
|
log = logging.getLogger(loggerName)
|
||||||
|
|
||||||
|
# Set our log output styles
|
||||||
|
fFormatter = logging.Formatter('[%(asctime)s] %(pathname)s:%(lineno)d %(levelname)s - %(message)s', '%m-%d %H:%M:%S')
|
||||||
|
cFormatter = logging.Formatter('%(pathname)s:%(lineno)d] %(levelname)s - %(message)s')
|
||||||
|
|
||||||
|
ch = logging.StreamHandler()
|
||||||
|
ch.setLevel(level=chLogLevel)
|
||||||
|
ch.setFormatter(cFormatter)
|
||||||
|
log.addHandler(ch)
|
||||||
|
|
||||||
|
if createFile:
|
||||||
|
folder = "core/logs"
|
||||||
|
file = folder + "/twitter-bot.log"
|
||||||
|
|
||||||
|
fh = logging.FileHandler(file)
|
||||||
|
fh.setLevel(level=fhLogLevel)
|
||||||
|
fh.setFormatter(fFormatter)
|
||||||
|
log.addHandler(fh)
|
||||||
|
|
||||||
|
return log
|
|
@ -0,0 +1,2 @@
|
||||||
|
from .Logger import Logger
|
||||||
|
from .Browser import Browser
|
Loading…
Reference in New Issue