PyQT : How to pass a webdriver object from QThread to UI thread? - python

I'm making a program that automatically enters to a website and login(basically using selenium and chrome webdriver). User can type own information(id&pw) and site address in a dialog(used PyQt4 module). After finishing it, pressing ok button will execute it.
After logging in, I want to do some other actions with that webdriver objects.
So my question is, how can I pass the webdriver object to the main thread(which is UI thread here) so that I can do some other actions(such as logging out, etc), or how to manage that webdriver object generated in other thread in main thread?
I'm using Python3.7.4 and PyQt4 version.
I googled similar questions and found that it might be related with signal&slots.
so I tried to imitate this example (https://nikolak.com/pyqt-threading-tutorial/) which uses custom signal.
In this example, it passes a QString instance to the main thread(UI thread).
So I tried to pass my webdriver object by imitating it, but It's not going well...
The basic structure of code is following:
class MyDialog(QDialog):
def __init__(self):
QDialog.__init__(self)
# and some codes with widget layouts
def btnOkClicked(self):
a = [self.editSite1.text(), self.editId.text(), self.editPw.text()]
self.gothread = goWebsiteThread(a)
# 'goWebsiteThread' is a thread that generates webdriver object and executes login function
self.connect(self.gothread, SIGNAL("add_driver(PyQt_PyObject)"), self.add_driver)
# this line is what I tried to pass the driver object to this main thread
self.gothread.start()
class goWebsiteThread(QThread, QObject):
# I tried to pass this class's object by making this class inherit QObject class... sorry for unfounded try..
def __init__(self, sites):
QThread.__init__(self)
self.sites = sites
def goWebsite(self):
self.driver = webdriver.Chrome('./chromedriver.exe', options=options)
self.driver.get(some site address that user typed)
# and codes to log in
self.emit(SIGNAL('add_driver(PyQt.PyObject)'), self.driver)
# I tried to pass the driver object by emitting signal...
def run(self):
self.goWebsite()
But this doesn't work (MyDialog object doesn't recognize the driver object).
How can I properly pass the webdriver object to MyDialog object and use it?

Webdriver should run on a new thread and it would be impossible to control webdriver from a UI thread.
But you can still store the webdriver as a member variable of the UI instance.
If you want to send some commands to the webdriver, you would need to start a new thread and handle automation works in the newly created thread.

Related

PyQt5 Threading a Selenium Script

I'm having difficulties figuring out threading in PyQt5. I made a GUI for a script I use at work in order to share with my coworker. The script does selenium browser automation with some variables that it receives from the GUI. The script is located in a different .py file and it doesn't inherit from any class I just instantiate it in the mainapp.py when I click the query button. (Oh and the script doesn't return anything, I added a return True for testing purposes)
mainapp.py
class OutputCheckerSub(Ui_OutputChecker):
def __init__(self):
super().__init__()
........
........
# Connecting Query button
self.query_button.clicked.connect(self.run_output_script)
def run_output_script(self):
self.output_script.run_script()
......
This launches the script with the proper variables but crashes the GUI app.
I managed to get threading somewhat working (W/O setting the variables) but it crashes after 10 seconds.
multithreading.py
class OutputScriptThread(QThread):
signal = pyqtSignal(bool)
def __init__(self):
super().__init__()
def run(self):
output_checker_script = output_checker.OutputCheckerScript()
state = output_checker_script.run_script()
self.signal.emit(state)
mainapp.py with faulty threading
class OutputCheckerSub(Ui_OutputChecker):
def __init__(self):
super().__init__()
.......
.......
# Connecting Query button
self.query_button.clicked.connect(self.run_output_script)
self.outputcheck_thread = multithreading.OutputScriptThread()
def run_output_script(self):
self.outputcheck_thread.start()
.....
One more challenging part is passing variables from one class to another. currently my GUI directly modifies the variables of the instanciated script . If I get the threading working where should I set the variables ? From the Thread?

Using Selenium inside a class

I'm not very experience with writing classes and somewhat confused by their behavior.
test.py
class Profile:
def __init__(self):
self.browser = webdriver.Chrome()
def browser_object(self):
return webdriver.Chrome()
from the terminal:
from test import Profile
# I thought this would initialize webdriver.Chrome() and a browser window would pop up
x = Profile.browser_object()
# trying to access the webdriver so I can do something like
x.get(url) #from the terminal
No browser window is popping up. When I run x I see that it is a <function Profile.browser_object ...>
What am I doing wrong?
Try changing your other script to actually make an instance of the class you defined in test.py.
from test import Profile
profile_instance = Profile() # make an instance
browser_I_made = profile_instance.browser_object() # you probably wouldn't do this, but it's possible
I don't think you understand that a class is like a factory and you get "instances" of that object off the assembly line like in profile_instance = Profile()

python wrapper; correct naming for method calling

I want to wrap the selenium webdriver in my own class, that each time I will call a method from my class it will handle calling and error handling to the webdriver class. what is the correct way to do it ?
class myClass():
browser = ... selenium web driver ...
def find_element_by_xpath(self, value):
try
browser.find_element_by_xpath(value)
except:
....
can myClass have the same method name ?
There are multiple valid ways to handle the calling and error handling of the webdriver class and yours should be fine.
Yes, myClass can have the same method names but you need to make sure you're calling the right thing. E.g
myClassInstance = myClass()
myClassInstance.find_element_by_xpath('thing')
will call browser.find_element_by_xpath just fine

Python: Making PhantomJS browser shut down if test terminated

I'm working on a Django app. I'm using Selenium together with PhantomJS for testing.
I found today that I every time I terminate the test (which I do a lot when debugging,) the PhantomJS process is still alive. This means that after a debugging session I could be left with 200 zombie PhantomJS processes!
How do I get these PhantomJS processes to terminate when I terminate the Python debug process? If there's a time delay, that works too. (i.e. have them terminate if not used for 2 minutes, that would solve my problem.)
The usual setup is to quit the PhantomJS browser in the teardown method of the class. For example:
from django.conf import settings
from django.test import LiveServerTestCase
from selenium.webdriver.phantomjs.webdriver import WebDriver
PHANTOMJS = (settings.BASE_DIR +
'/node_modules/phantomjs/bin/phantomjs')
class PhantomJSTestCase(LiveServerTestCase):
#classmethod
def setUpClass(cls):
cls.web = WebDriver(PHANTOMJS)
cls.web.set_window_size(1280, 1024)
super(PhantomJSTestCase, cls).setUpClass()
#classmethod
def tearDownClass(cls):
screenshot_file = getattr(settings, 'E2E_SCREENSHOT_FILE', None)
if screenshot_file:
cls.web.get_screenshot_as_file(screenshot_file)
cls.web.quit()
super(PhantomJSTestCase, cls).tearDownClass()
If you do not use unittest test cases, you'll have to use the quit method yourself. You can use the atexit module to run code when the Python process terminates, for example:
import atexit
web = WebDriver(PHANTOMJS)
atexit.register(web.quit)

Managing multiple instances of Selenium in Python

I am trying to be able to manage multiple instances of Selenium at the same time, but haven't had much luck. I'm not 100% sure if it's possible. I have an application with a GUI built with PyQT that retrieves our client's information from our SQL database. It's a fairly simple app that lets our users easily log in and out of our clients' accounts. They click the client's name, press "Login", it launches an instance of Firefox, logs into the account, and stays open so the user can do whatever they need to do. When they are done, they click the "Logout" button, and it logs out of the account and quits the webdriver instance.
What I'm trying to provide is a way for them to log into multiple accounts at once, while still maintaining the ability to click one of the client's names that they are logged into, process the logout on that account, and close that browser instance.
One thing I was hoping is to be able to control the webdriver by either a process ID, or unique ID, in which I can store in a dictionary linking it to that client, so when they click the client's name in the app, and press logout, it uses something in PyQT like "client_name = self.list_item.currentItem().text()" to get the name of the client they have selected (which I'm already using for other things, too), finds the unique ID or process ID, and sends the logout command to that instance, and then closes that instance.
This may not be the best way to go about doing it, but it's the only thing I could think of.
EDIT: I also know that you can retrieve the Selenium session_id with driver.session_id (considering your webdriver instance is assigned as 'driver'), but i have seen nothing so far on being able to control a webdriver instance by this session_id.
EDIT2: Here is an incredibly stripped down version of what I have:
from selenium import webdriver
from PyQt4 import QtGui, QtCore
class ClientAccountManager(QtGui.QMainWindow):
def __init__(self):
super(ClientAccountManager, self).__init__()
grid = QtGui.QGridLayout()
# Creates the list box
self.client_list = QtGui.QListWidget(self)
# Populates the list box with owner data
for name in client_names.itervalues():
item = QtGui.QListWidgetItem(name)
self.client_list.addItem(item)
# Creates the login button
login_btn = QtGui.QPushButton("Login", self)
login_btn.connect(login_btn, QtCore.SIGNAL('clicked()'), self.login)
# Creates the logout button
logout_btn = QtGui.QPushButton("Logout", self)
logout_btn.connect(logout_btn, QtCore.SIGNAL('clicked()'), self.logout)
def login(self):
# Finds the owner info based on who is selected
client_name = self.client_list.currentItem().text()
client_username, client_password = get_credentials(client_name)
# Creates browser instance
driver = webdriver.Firefox()
# Logs in
driver.get('https://www.....com/login.php')
driver.find_element_by_id('userNameId').send_keys(client_username)
driver.find_element_by_id('passwordId').send_keys(client_password)
driver.find_element_by_css_selector('input[type=submit]').click()
def logout(self):
# Finds the owner info based on who is selected
client_name = self.client_list.currentItem().text()
# Logs out
driver.get('https://www....com/logout.php')
# Closes the browser instance
driver.quit()
def main():
app = QtGui.QApplication(sys.argv)
cpm = ClientAccountManager()
cpm.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You can have multiple drivers. Just call webdriver.Firefox() multiple times and keep references to each driver. Some people report oddball behavior, but it basically works.
driver.close() will close the browser and does not take an id.

Categories

Resources