Looking and Performing action through xpath variables in Selenium - Python - python

I have the following page layout:
<div class="x1">
<button aria-expanded="false" class="button" data-toggle="dropdown" type="button">Actions<span class="caret"></span></button>
<ul class="dropdown-menu" role="menu">
<li>
Start
</li>
<li>
<a data-target="delete" data-toggle="modal">Delete</a>
</li>
<li>
Authenticate
</li>
</ul>
</div>
Each one of these represent a button ("Action"), which when clicked will present a dropdown menu between Start, Delete, and Authenticate.
I'm trying to automate my page test from the backend using Selenium and Python.
Here what I have so far:
def scan_page(action):
scan_url = urlparse.urljoin(self.base_url, "foo")
self.driver.get(scan_url)
elements = self.driver.find_elements_by_xpath("//div[#class='x1']")
# loop over all the elements found by xpath
for element in elements:
element.click()
ul = element.find_element_by_class_name("dropdown-menu")
if action == "scan":
if "Start" in ul.text:
# start the action
button = element.find_element_by_xpath("//button[#data-toggle='dropdown']")
button.click()
elif action == "delete":
if "Delete" in ul.text:
self.log.info("Deleting...")
elif action == "auth":
if "auth" in ul.text:
self.log.info("Authenticating...")
else:
continue
The function is given an action, which define what is to be done. If "start" then loops over all the elements and click the Start option. However, it doesn't look like it's going over all the buttons defined in the page, and only the first button is being started. So, for some reason, the Delete option is somehow selected.
What am I doing wrong?
Update:
I've added a JS Fiddle so people can play with that:
http://jsfiddle.net/58rs3kuk/1/

I found and fixed a few issues. I think this should work or at least get you on the right track.
1) You were clicking the container DIV which isn't necessary. It probably doens't hurt, it just doesn't do anything productive.
2) You were clicking BUTTON only if action = "scan". You want to click BUTTON for all actions so those lines need to move up before the IF starts. So... you will 1. click the BUTTON, 2. determine what action was requested, and 3. perform that action.
3) You were grabbing the UL to get the text but the text you want is in the LI (but is not needed after the changes I made).
4) I changed the names of some of the variables to make them more identifiable, elements is now buttonContainers and element is now buttonContainer. I would suggest you follow this practice because it will help you (and others) debug your code more easily.
5) Since we know the order of the actions in each button and they are all the same, we don't have to look for the text in the link... we can just click them by index: 0 for Start, 1 for Delete, 2 for Authenticate.
6) Added a warning in the logs if an invalid action is passed. You should probably change that to an exception or whatever is appropriate in your scripts.
7) Fixed the indent of the code you posted
def scan_page(action):
scan_url = urlparse.urljoin(self.base_url, "foo")
self.driver.get(scan_url)
buttonContainers = self.driver.find_elements_by_xpath("//div[#class='x1']")
# loop over all the buttonContainers
for buttonContainer in buttonContainers:
buttonContainer.find_element_by_tag_name("button").click()
lis = buttonContainer.find_elements_by_tag_name("li")
if action == "start":
lis[0].click() # because we know the order of the actions and they are the same in each button, we can just click on them by index.
self.log.info("Starting...")
elif action == "delete":
lis[1].click()
self.log.info("Deleting...")
elif action == "auth":
lis[2].click()
self.log.info("Authenticating...")
else:
self.log.info("WARNING: " + action + " is not a valid action.")
continue

Related

Element <span> is not clickable at point (1039,84) because another element <div class="_2dDPU CkGkG"> obscures it

Im trying to click the like button with selenium this is the exception error I'm getting:
[-] Passed One Message: Element <span class="glyphsSpriteHeart__filled__16__white u-__7"> is not clickable at point (1039,84) because another element <div class="_2dDPU CkGkG"> obscures it
I have tried solving it with a loop that clicks all of the aria label like elements with Xpath, see my code:
while count <= likes:
try:
likeopt = bot.find_elements_by_xpath("//*[#aria-label='Like'] ")
for likeopti in likeopt:
likeopti.click()
#bot.find_element_by_class_name("fr66n").click()
print("[+] Liked !", count)
count += 1
time.sleep(random.randint(8, 15))
bot.find_element_by_class_name("_65Bje").click()
time.sleep(random.randint(8, 15))
except Exception as e:
try:
bot.find_element_by_class_name("_65Bje").click()
time.sleep(random.randint(3, 7))
print("[-] Passed One", e)
except Exception as x:
bot.find_elements_by_xpath("//*[#aria-label='Close'] ")
continue
print(x,"[+]click on X, finished all posts")
How do you think can it can be solved?
Thanks
Watch your script running, and pay attention to the point where it fails. You will likely see that there's some sort of drop down menu, pop-up 'dialog', or even an advertisement, that has displayed over your target link. You'll need to close/dismiss this to be able to get at the target link.
Alternatively, you can hack it by instructing the driver to send a click command via Javascript. bot.execute_script("arguments[0].click();", likeopt) I typically save this approach as a last resort since it does not represent how a user would interact with the page.

How to make only 1 refresh on multiple clicks python/django

I have a site using GCP python API, which is quite slow at making pull requests. So I have cached the area of the template using this data, and in the view I check if the cache is still active before making any more requests (not a copy n pasted code so if theres any typos ignore em :) )
def gcpProjectsView(request):
gcpprojects = None
cached_time = None
if cache.get(make_template_fragment_key('gcp')) is None:
cached_time=timezone.now()
gcpprojects = get_gcp_projects()
return render (request , 'gcp/gcpprojects.html', {'gcpprojects':gcpprojects,'last_update':cache_time})
To manually update the data, I have a refresh button that points to this view:
def refresh_gcp(request):
cache.delete(make_template_fragment_key('gcp'))
return redirect('gcpProjectsView')
The problem is that if the user clicks the refresh button 5 times the view makes 5 GCP Api calls, it needs to only do 1. How do I resolve this issue?
My suggestion would be to use a simple form with a button. Buttons can be disabled using simple javascript when clicked.
Because you're redirecting back to the same template in the view the button should be re-enabled again once it has 'refreshed'.
In this example snippet replace the action with whatever url routes to refresh_gcp.
<form method="GET" action="<url that points to refresh_gcp>">
<button type="submit" onclick="this.disabled = true;">Refresh</button>
</form>
This was the simplest solution I could think of that didn't involve implementing some sort of token validation for requests.
Use django session :
def refresh_gcp(request):
clicked = request.session.get("click")
if clicked:
return render(request,"your_html",{"disable":True})
# Set Button disable or not in your html by using context...
else:
request.session["click"]="user_click"
cache.delete(make_template_fragment_key('gcp'))
return render(request,"your_html",{"disable":False})

Python Selenium hover action concatenates in memory

I am testing a website which has a menu with submenus appearing on hover.
I have created a function to interact with this menu:
def go_to(navbar_item, menu_item):
# find the navbar item
assets_main_menu = driver.find_element(By.ID, navbar_item)
#hover over the navbar item, so the submenu appears
hover.move_to_element(assets_main_menu).perform()
# find the submenu item
xpath = "//*[contains(text(), \'" + menu_item + "\')]"
destination = driver.find_element_by_xpath(xpath)
# hover over the submenu item and clicks
hover.move_to_element(destination).click().perform()
The problem is that i use this function more than once such as:
# action 1
go_to('navbar item1 id', 'submenu item1')
do_something()
# action 2
go_to('navbar item1 id', 'submenu item2')
do something()
# action 3
go_to('navbar item1 id', 'submenu item3')
do_something()
selenium actually repeats the previous steps going through the past menu items like:
ACTUAL OUPTUP
action 1, do something -> action 1, action 2, do something -> action 1, action 2, action 3, do something
Instead my DESIRED OUTPUT would be:
action 1, do something -> action 2, do something -> action 3, do something
I tried unsetting the variables:
navbar_item, menu_item, hover, xpath, destination.
at the end of the function with no luck.
I have also tried to instantiate hover within my function
hover = ActionChains(driver);
but in this last attempt my code stopped working.
When you call an action chain, perform() does not clear out the previous steps. You've only really shared your function so the real culprit is the structure of your code and how python consumes variables.
I note in your function, you pass in two strings but your function knows what driver and hover are. Which sounds like you're using global variables.
To demo your problem I created this simple page for you with a click counter:
<html>
<body>
<button id="button" onclick="document.getElementById('input').value = parseInt(document.getElementById('input').value) + 1">Click me</button>
<input id="input" value="0"></input>
</body>
</html>
It's a flat page that just knocks up a number each time you press the button:
Then, to show you what's happening, i created a similar version of your code:
driver = webdriver.Chrome()
driver.implicitly_wait(10)
driver.get(r"c:\git\test.html")
actions = ActionChains(driver)
def ClickByActions(element):
actions.move_to_element(element).click().perform()
#find the button and click it a few times...
button = driver.find_element_by_id('button')
ClickByActions(button)
ClickByActions(button)
ClickByActions(button)
With this, you expect the end click-count-value to be 3. However, it is 6.
Same as your problem. First call does +1, second call does +1 +1, third call does +1 +1 +1.
Finally! the solution - create action chain in the function with your driver:
def ClickByActions(element):
localActions = ActionChains(driver)
localActions.move_to_element(element).click().perform()
I note in the comments you say you tried this. Can you please try:
not using hover but another name -
pass in driver instead of relying on it being a global variable. For this you would use go_to(navbar_item, menu_item, driver)
Apparently hover.reset_actions() should also work - but this didn't work for me.
If these don't work, please share your site URL so I can try on your actual site or say what the error is and describe what happens.

Selenium + Python how to listen for change of element

The idea is to create a bot to read message from a chat, and all message are in a ul>li(not neccesary to write message), e.g
<ul class="message-list">
<li class="message">
Hello There
</li>
<li class="message">
Hello
</li>
</ul>
I found expected_conditions from Selenium, to handle if element exist/found, but it's always true, because there is a message but I don't want to continue the code if a message was already in chat, that's not the main idea. And after that, I found EventFiringWebDriver, AbstractEventListener and nothing.
from selenium import webdriver as wb
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.events import EventFiringWebDriver, AbstractEventListener
from selenium.webdriver.common.keys import Keys
class MyListener(AbstractEventListener):
def before_navigate_to(self, url, driver):
print("Before navigate to %s" % url)
def after_navigate_to(self, url, driver):
print("After navigate to %s" % url)
driver = wb.Chrome('C:\python\selenium\chromedriver.exe')
wait = WebDriverWait(driver, 300)
# ef_driver = EventFiringWebDriver(driver, MyListener())
driver.implicitly_wait(15)
driver.get('https://socket.io/demos/chat/')
driver.switch_to.frame(driver.find_element_by_class_name('iframe-class'))
InputName = driver.find_element_by_xpath('/html/body/ul/li[2]/div/input')
InputName.send_keys('MyNameIsJeff')
InputName.send_keys(Keys.ENTER)
I think you can write a loop and inside the loop, get the number of "li"s inside the desired "ul". if the number of "li"s increased, you have a new message.
# get list of li inside of ul
ulList = driver.find_elements_by_xpath("//ul[#class='message-list']")
lenOfLis = len(ulList)
# wait for new message...
while (true):
ulList = driver.find_elements_by_xpath("//ul[#class='message-list']")
if (len(ulList) > lenOfLis): # you have new message
for (i in range(len(ulList)-lenOfLis)):
yourLastMessage = ulList[-1-(i-1)]
# you can do whatever you want with your last messages
LenOfLis = len(ulList) # update length of ul
# wait 15 seconds
sleep(5)
You can do some kind of listener in a loop that checks the text of your message elements to determine whether or not it is a new message, you just have to determine a set time frame that you want to wait between 'checks' -- 10 seconds, 30 seconds, 1 minute, etc.
I don't think you need an event listener for this. You can just grab the latest message, then keep checking it to see if it's different than the previous value.
from time import sleep
# get list of currently displayed messages
messages = driver.find_elements_by_xpath("//li[#class='message']")
# get text from most recent message
# depending on element order on the page, might need to do messages.last
last_message_text = messages[0].text
# do something here to trigger a new message coming in?
# wait for last_message_text to be something different
while (true):
# get list of currently displayed messages
messages = driver.find_elements_by_xpath("//li[#class='message']")
# new message has arrived if we hit this statement
if (messages[0].text != last_message_text) break;
# wait 15 seconds
sleep(15)
This example will grab the list of messages currently displayed. Then, it grabs the last received message. The code enters a loop and re-fetches the list of messages, checks the last received message, and compares its text to the last message value we saved earlier.
If the last received message is different from saved value, the loop will break, meaning a new message has arrived.
There's a few unclear things here -- the most recent message may be either the first element, or last element, in messages list. The other issue -- are you doing something on your end to trigger a new message appearing? Such as InputName.send_keys('MyNameIsJeff')? If this is the case, the order of the code may need to change a bit.
Lastly -- in your comments, you mentioned sometimes the ul is empty. I'm not really sure what this means, but if some messages are appearing and there is no HTML on the page for them, then this solution won't really work.

Selenium hangs when using actionchain().move then actionchain.click or mouse_up

I'm using selenium2library for automation testing for drag drop action. I'm running on windows 8 64bit, selenium 2.48.0, ride.py. Browser used to test: firefox and chrome latest stable version
what I did was to create a dummy html page with input text and a link, and try drag that link into the input text
Here is the html:
<div id="wrapper">
<input id="target" type="text" style="width:200px; height:50px" />
</div>
<a id="source" href="http://google.com" >drag me </a>
And here is my python code for automation:
class CustomSeleniumLibrary(Selenium2Library):
...
def test_drag(self):
self.open_browser("http://localhost:8080/a.html", "firefox")
source = self._element_find("//a[#id='source']", True, True)
target = self._element_find("//input[#id='target']", True, True)
drag = ActionChains(self._current_browser()).click_and_hold(source)
moveDum = ActionChains(self._current_browser()).move_by_offset(1,1)
move = ActionChains(self._current_browser()).move_to_element_with_offset(target,1,1)
#I have also tried ActionChains().drag_and_drop().perform() or make a dummy move move_by_offset followed by move_to_element_with_offset but no use
drag.perform()
moveDum.perform()
move.perform()
What I found is when the move finish or mouse_down() finish, the next action is not performed, I can see the link get holded, but no move action performs until I manually move my mouse on the browser. ride.py UI flicks at that time and the request:
16:24:47.042 : DEBUG : POST http://127.0.0.1:58095/hub/session/fa7590b6-396f-4cb5-a08a-e35138a9216e/moveto {"sessionId": "fa7590b6-396f-4cb5-a08a-e35138a9216e", "element": "{6586b4ae-3c51-4e18-bb40-e006af369768}", "xoffset": 1, "yoffset": 1}
hangs forever until I move the mouse manually on the browser
Do anyone of you got the same problem, or did I do something wrong? And do you have any suggestion for using draganddrop feature using robotframework selenium2library?
Best regards,
Dan
I cannot check it, but as I remember ActionChains works in this way:
actions = ActionChains(self._current_browser())
actions.click_and_hold(source)
actions.move_by_offset(1,1)
actions.move_to_element_with_offset(target,1,1)
actions.perform()
Let me know if this code works incorrectly

Categories

Resources