Wait till text in an element is changed - python

Please suggest if Selenium has a good option to wait until text will be changed within an element.
Conditions:
Page is not autoreloaded
Element which text I need is dynamically reloaded
Time required for this data to be updated is unknown
Expected text is unknown. It is a timestamp.
I wrote a method which checks it every 1 second (or any time I set) and gets the value when it is changed.
But I think there may be some better options. I try to avoid using time.sleep, but in the current case this is the only option I came to.
Note, this text may change even after 5 minutes, so I will need to adjust the number of retries retries or time.sleep()
Also, I use print just for debugging.
def wait_for_text(driver):
init_text = "init text"
retry = 1
retries = 120
start_time = time.time()
while retry <= retries:
time.sleep(1)
field_text = driver.find_element_by_id("my_id").text
if field_text != init_text:
break
retry += 1
now = time.time()
total_time = now-start_time
print(f"Total time for text to change {total_time}")
print(f"Final field value: {field_text}")

You can do the thing like this:
driver.get("https://stackoverflow.com")
init_text = "Log in"
from selenium.webdriver.support.wait import WebDriverWait
waiter = WebDriverWait(driver=driver, timeout=120, poll_frequency=1)
waiter.until(lambda drv: drv.find_element_by_class_name("login-link").text != init_text)
print("Success")
driver.quit()
So you are basically waiting for an element stops being equal to a given value.
P.S. - You can test the snippet above by changing the button text in dev tools.

You can loop until the text change to value you want
def wait_for_text(driver):
init_text = "init text"
retry = 1
while(driver.find_element_by_id(".") != init_text ):
retry += 1
print(driver.find_element_by_id(".").text)
print("number of tries: " + retry)

Related

Is there a way to check if request history contains more than one item?

Imagine this, a program that lists out all the redirects that a link has (i.e a grabify link.)
Now lets say you wanted to print "yes" if there was more than 1 link pasted, how can I do that?
This is the code that I have:
import requests
import time
def nowdotheyes():
time.sleep(1)
print("...")
time.sleep(3)
responses = requests.get(link)
for responses in responses.history:
print(responses.url)
if (responses.history > 1):
print ("yes")
Here Is the finalized code, I provided a boolean and an int value for you to use in your program if needed. The "Icarly domain is the only one I know that redirects off hand"
import requests
def check(link):
redirects = None
responses = requests.get(link)
print(responses.url)
if len(responses.history) >= 1:
redirects = True
print ("Redirects More Than One Time")
elif len(responses.history) <= 1:
redirects = False
print("Does Not Redirect More Than one time")
total = int(len(responses.history))
print(f"Total Number of Redirects {total}") # Just so you have the total number of redirects if you need it
print(f"Redirect Status: {redirects}") # A boolean that returns if it redirects
check(link="https://www.icarly.com")
Thanks to #Kim Kakan Andersson, I realized the problem. I should have removed the for loop and added lens. Thanks everyone.

In Python how would one wait a specific time while waiting for an image to possibly occur?

More specifically, I have a Sikuli code that works fine for the most part. However, during my 5400 second wait sometimes I have a network connection error that pops a button on the screen I need to click that occurs at no specific time during those 5400 seconds. Also, if that network button occurs and I click it I need to start the code over from the beginning.
I assume I can do a nested While loop waiting for that network connection button to pop up and if it doesn't occur continue with the code but how would I write that? Here's the code currently:
while True:
exists("start.png", 10*60)
click(Pattern("start.png").targetOffset(-1,0))
wait("enter.png", 5)
click(Pattern("enter.png").targetOffset(2,30), 5)
wait(5400)
type(Key.ESC)
exists("leave.png", 5)
click(Pattern("leave.png").targetOffset(-11,0))
#the following if statements could or could not occur and could come in no specific order. This part of the code could be incorrect
if exists("close1.png", 5):
click(Pattern("close1.png").targetOffset(0,1))
elif exists("close2.png", 20):
click("close2.png")
elif exists("close3.png", 20):
click("close3.png")
wait(5)
A suggestion from RaiMan from SikuliX (not tested ;-)
"""
#the following if statements could or could not occur and could come in no specific order.
#This part of the code could be incorrect
if exists("close1.png", 5):
click(Pattern("close1.png").targetOffset(0,1))
elif exists("close2.png", 20):
click("close2.png")
elif exists("close3.png", 20):
click("close3.png")
"""
netError = False
def handler(event):
global netError
netError = True
click(event.getMatch())
onAppear("close1.png", handler)
onAppear("close2.png", handler)
onAppear("close3.png", handler)
while True:
netError = False
observeInBackground(FOREVER)
#exists("start.png", 10*60)
#click(Pattern("start.png").targetOffset(-1,0))
click(wait("start.png", 1))
#wait("enter.png", 5)
#click(Pattern("enter.png").targetOffset(2,30), 5)
click(wait(Pattern("enter.png").targetOffset(2,30), 5)
towait = 5400
while not netError and toWait > 0:
wait(1)
towait -= 1
if netError:
stopObserver()
continue
type(Key.ESC)
#exists("leave.png", 5)
#click(Pattern("leave.png").targetOffset(-11,0))
click(wait(Pattern("leave.png").targetOffset(-11,0), 5)
stopObserver()
wait(5)
Here is some alternative without observer events. I personally find it simpler to work in this way :-).
I used your original code and just replaced wait(5400) with a time based loop that checks for images that might occur.
import time
while True:
exists("start.png", 10*60)
click(Pattern("start.png").targetOffset(-1,0))
wait("enter.png", 5)
click(Pattern("enter.png").targetOffset(2,30), 5)
t0 = time.time()
while (time.time()-t0 < 5400):
#the following if statements could or could not occur and could come in no specific order. This part of the code could be incorrect
if exists("close1.png", 5):
click(Pattern("close1.png").targetOffset(0,1))
elif exists("close2.png", 20):
click("close2.png")
elif exists("close3.png", 20):
click("close3.png")
type(Key.ESC)
exists("leave.png", 5)
click(Pattern("leave.png").targetOffset(-11,0))
wait(5)

Use Python and Selenium to output text of only selected index

I'm having some trouble with creating a program that automates a checkout process. I'm using python 3 along with Selenium. The program parses through a range of dates, which are outputted on the page as available four available slots. If none are available on the current page, it will click the 'next' button and search through the next four dates. If it gets to the end of the available date ranges and finds nothing, it'll wait thirty seconds and reset and do it all over again.
I've got the majority of this done, except for two issues:
1) I'm trying to add an argument that, when included, will go beyond the base functionality (which is to simply notify the user via text using Twilio), and complete the full checkout process.
This is the python code I'm using:
def find_available(args):
dates_available = True
spaces_free = False
free_spaces = ""
while not spaces_free:
while dates_available:
time.sleep(1.5)
spots = driver.find_elements_by_css_selector('.ss-carousel-item')
for spot_index, spot in zip(range(date_range), spots):
if spot.value_of_css_property('display') != 'none':
spot.click()
available_dates = driver.find_elements_by_css_selector('.Date-slot-container')
for available_date in available_dates:
if available_date.value_of_css_property('display') != 'none':
selected_spot = available_date.find_element_by_css_selector('#slot-container-UNATTENDED')
if 'No doorstep delivery' not in selected_spot.text:
free_spaces = selected_spot.text.replace('Select a time', '').strip()
spaces_free = True
else:
print(selected_spot.text.replace('Select a time', '').strip())
if spaces_free:
print('Slots Available!')
if args.checkout:
client.messages.create(to=to_mobilenumber,
from_=from_mobilenumber,
body=free_spaces)
driver.find_element_by_xpath("//*[contains(text(), 'Soonest available')]").click()
time.sleep(1.5)
driver.find_element_by_xpath("//input[#type='submit' and #value='Continue']").click()
print('Your order has been placed!')
else:
client.messages.create(to=to_mobilenumber,
from_=from_mobilenumber,
body=free_spaces)
print('Your order time will be held for the next hour. Check your date and confirm!')
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="auto-checkout")
parser.add_argument('--checkout', '-c', action='store_true',
help="Select first available slot and checkout")
args = parser.parse_args()
find_available(args)
Expected Behavior
If the program is launched using the '--checkout' or '-c' argument, then, once 'spaces-free' is set to true, it should send a text with the text within the 'free_spaces' element. It should then move on to the next phase, which would be a selecting of a radio button that contains the text 'Soonest available' (as in select the first available radio button that contains an available time slot), and then click the continue button.
Actual Behavior
The program will run, find an available time slot, then simply move on to the next days, never attempting to select a radio button and move forward in the checkout process.
What am I doing wrong?
Any help would be appreciated.
it seems to me that you never set the dates_available to False inside your while loop:
while dates_available:
time.sleep(1.5)
spots = driver.find_elements_by_css_selector('.ss-carousel-item')
for spot_index, spot in zip(range(date_range), spots):
if spot.value_of_css_property('display') != 'none':
spot.click()
available_dates = driver.find_elements_by_css_selector('.Date-slot-container')
for available_date in available_dates:
if available_date.value_of_css_property('display') != 'none':
selected_spot = available_date.find_element_by_css_selector('#slot-container-UNATTENDED')
if 'No doorstep delivery' not in selected_spot.text:
free_spaces = selected_spot.text.replace('Select a time', '').strip()
spaces_free = True
else:
print(selected_spot.text.replace('Select a time', '').strip())
So you'll never exit the while loop. If you don't want to rewrite the whole logic, you could set dates_available = False right after you set spaces_free = True. That would allow exiting the while loop, but you might need a break or two to exit the for loops too.
If you want a failsafe behavior, you should refactor your code for smaller functions and if you want only the first available something, you could just return from the function with the first available data.
Something like this maybe?
def find_available(args):
def get_a_date():
while True:
time.sleep(1.5)
spots = driver.find_elements_by_css_selector('.ss-carousel-item')
for spot_index, spot in zip(range(date_range), spots):
if spot.value_of_css_property('display') != 'none':
spot.click()
available_dates = driver.find_elements_by_css_selector('.Date-slot-container')
for available_date in available_dates:
if available_date.value_of_css_property('display') != 'none':
selected_spot = available_date.find_element_by_css_selector('#slot-container-UNATTENDED')
if 'No doorstep delivery' not in selected_spot.text:
return selected_spot.text.replace('Select a time', '').strip()
else:
print(selected_spot.text.replace('Select a time', '').strip())
free_spaces = get_a_date()
print('Slots Available!')
if args.checkout:
client.messages.create(to=to_mobilenumber,
from_=from_mobilenumber,
body=free_spaces)
driver.find_element_by_xpath("//*[contains(text(), 'Soonest available')]").click()
time.sleep(1.5)
driver.find_element_by_xpath("//input[#type='submit' and #value='Continue']").click()
print('Your order has been placed!')
else:
client.messages.create(to=to_mobilenumber,
from_=from_mobilenumber,
body=free_spaces)
print('Your order time will be held for the next hour. Check your date and confirm!')

Python - How to loop until site is down using requests and threads

So what I want to do is that I want to make a sorts of monitor a website that pick ups a random number. Before it does it needs to requests to the website to see whenever it is valid or not. When it is live it will generate random numbers 1-100 and I want it to check every random 3-6 second and then print again the number and repeat until the website is down.
What I have tried to do is following:
def get_product_feed(url_site):
thread = url_site
password = False #We start by giving a false/true value
while not password: #If it is not True then we do the for loop
available = False
while not available:
try:
checkpass = requests.get(thread, timeout=12) #We requests the site to see if its alive or not
if ('deadsite' in checkpass.url): #If it contains etc deadsite then we enter the while True
while True:
contcheck = requests.get(thread,timeout=12) #We make new requests to see if its dead.
if ('deadsite' in contcheck.url): #if it is, then we sleep 3-7sec and try again
randomtime = random.randint(3, 7)
time.sleep(randomtime)
else: #If its not containig it anymore then we send the bs4 value
available = True
bs4 = soup(contcheck.text, 'lxml')
return bs4
break
else: #If its having either of them then we send instant.
bs4 = soup(contcheck.text, 'lxml')
return bs4
break
except Exception as err:
randomtime = random.randint(1, 2)
time.sleep(randomtime)
continue
def get_info(thread):
while True:
try:
url = thread
resp = requests.get(url, timeout=12) #We requests to the website here
resp.raise_for_status()
json_resp = resp.json() #We grab the json value.
except Exception as err:
randomtime = random.randint(1,3)
time.sleep(randomtime)
continue
metadata = json_resp['number'] #We return the metadata value back to get_identifier
return metadata
def get_identifier(thread):
new = get_info(thread) #We requests the get_info(thread):
try:
thread_number = new
except KeyError:
thread_number = None
identifier = ('{}').format(thread_number) #We return back to script
return identifier
def script():
url_site = 'https://www.randomsitenumbergenerator.com/' #What url we gonna use
old_list = []
while True:
for thread in get_product_feed(url_site): #We loop to see through get_product_feed if its alive or not
if get_identifier(thread) not in old_list: # We then ask get_identifier(thread) for the values and see if its in the old_list or not.
print(get_identifier(thread)
old_list.append(get_identifier(thread)
I added a comment to make it easier to understand what is going on.
The issue I am having now that I am not able to make get_identifier(thread) to run until the website is down and I want it to continue to print out until the website is live til it dies and that is my question! What do I need to do to make it happen?
My thoughts was to add eventually threads maybe that 10 threads are checking at the same time to see if the website is dead or not and give back the value as a print but I am not sure if that is my solution for the question.

Calling a function within a function within a class

So there's this website that posts something I want to buy at a random time of day for a limited amount of time and I want to write something to send a message to my phone when a new url is posted to that webpage.
I planned on doing this by counting the number of links on the page (since it's rarely updated) and checking it every 5 minutes against what it was 5 minutes before that, then 5 minutes later check it against what it was 10 minutes before that, 5 minutes later check what it was 15 minutes before that... and if it's greater than what it originally was, send a message to my phone. Here's what I have so far:
class url_alert:
url = ''
def link_count(self):
notifyy=True
while notifyy:
try:
page = urllib.request.urlopen(self.url)
soup = bs(page, "lxml")
links=[]
for link in soup.findAll('a'):
links.append(link.get('href'))
notifyy=False
print('found', int(len(links)), 'links')
except:
print('Stop making so many requests')
time.sleep(60*5)
return len(links)
def phone(self):
self= phone
phone.message = client.messages.create(to="", from_="",body="")
print('notified')
def looper(self):
first_count = self.link_count()
print('outside while')
noty = True
while noty:
try:
second_count = self.link_count()
print('before compare')
if second_count == first_count:
self.phone()
noty = False
except:
print('not quite...')
time.sleep(60)
alert = url_alert()
alert.looper()
As a test, I decided to set the if statement that determines whether or not to send a message as equal but the loop kept on running. Am I calling the functions within the looper function the right way?
It looks like you need to eliminate the try block, as it is now, if self.phone() takes an exception you will never leave the loop
def looper(self):
first_count = self.link_count()
while True:
if first_count != self.link_count():
self.phone()
break
time.sleep(60)

Categories

Resources