How to hold key down with Selenium - python

I am using Selenium with Python and Chrome. I am trying to hold down various keys -- specifically "w, a, s, and d." I have found Selenium's action_chains.key_press action as well as the elem.send_keys method. The problem with the first method is that it only holds the key down for as long as it takes to complete an action chain. The problem with the elem.send_keys method is that it does not have an element to send the keys to.
I am trying to control a web-browser based robot with W-A-S-D, so I need to hold keys down for varying durations.
I have tried the following:
action_chains = ActionChains(driver)
action_chains.key_down("w")
action_chains.key_up("w")
as well as:
action_chains.key_press(elem, "w")
for x in range (0, 100):
action_chains.perform()
time.sleep(.01)
Neither are ideal.

The current driver for Chrome (version 2.30) implements the previous protocol where holding a key is only supported for a modifier key (Control, Shift, Alt, Command).
So this code works with Firefox but fails with Chrome since the keyUp event is emitted for each keyDown:
action_key_down_w = ActionChains(driver).key_down("w")
action_key_up_w = ActionChains(driver).key_up("w")
endtime = time.time() + 1.0
while True:
action_key_down_w.perform()
if time.time() > endtime:
action_key_up_w.perform()
break
But, since version 2.30, the Chrome driver supports the send_command to directly send a raw command via the devtools protocol.
So as a workaround, you can call Input.dispatchKeyEvent to emit low level events.
This is a working example with Selenium/Chrome to hold the key w during a second:
from selenium import webdriver
import json, time
def dispatchKeyEvent(driver, name, options = {}):
options["type"] = name
body = json.dumps({'cmd': 'Input.dispatchKeyEvent', 'params': options})
resource = "/session/%s/chromium/send_command" % driver.session_id
url = driver.command_executor._url + resource
driver.command_executor._request('POST', url, body)
def holdKeyW(driver, duration):
endtime = time.time() + duration
options = { \
"code": "KeyW",
"key": "w",
"text": "w",
"unmodifiedText": "w",
"nativeVirtualKeyCode": ord("W"),
"windowsVirtualKeyCode": ord("W")
}
while True:
dispatchKeyEvent(driver, "rawKeyDown", options)
dispatchKeyEvent(driver, "char", options)
if time.time() > endtime:
dispatchKeyEvent(driver, "keyUp", options)
break
options["autoRepeat"] = True
time.sleep(0.01)
driver = webdriver.Chrome()
driver.get("https://stackoverflow.com/questions")
# set the focus on the targeted element
driver.find_element_by_css_selector("input[name=q]").click()
# press the key W during a second
holdKeyW(driver, 1.0)

Selenium actions chain Should only be used with modifier keys (Control, Alt and Shift). So you want to press only the character w-a-s-d. so that, it didn't work.
You can use any gui automation tools like pyautogui, etc.
please try below code and let me know.
import pyautogui
pyautogui.PAUSE = 10
pyautogui.keyDown('w')
pyautogui.keyUp('w')
pyautogui.PAUSE=10 command make 10 seconds pause after each PyAutoGUI call

According to the Selenium Documentation for key_down, it states:
Should only be used with modifier keys (Control, Alt and Shift).
I've searched through the docs for an alternative solution, but it appears the behavior to "hold down" non-modifier keys is not possible in Selenium.

ActionChains(driver).key_down("w").pause(0.1).key_up("w").perform()
By chaining key_down and key_up with a .pause(0.1) in between you can hold down keys for any custom duration.
Even though key_down should only be used with modifier keys according to the docs, it turns out that it can be used with any key.

Related

python selenium datepicker gets cleared

I'm developing a python app that sets form componentes of a certain webpage (developed using vue3.js)
I'm able to set a datepicker's value, but, after that, next operation clears the dapicker away.
I must be doing something really fool, but I'm out of ideas.
Here's my code:
import sys
from selenium import webdriver
#-----------------------------------------------------------------------------------------------------------------------
driver_headfull = None
try:
driver_headfull = webdriver.Firefox()
firefox_options = webdriver.FirefoxOptions()
except Exception as e:
print('ERROR WHEN CREATING webdriver.Firefox()')
print("Please, verify you have installed: firefox and that geckodriver.exe's is in %PATH%")
print(e)
sys.exit(5)
#navigate to url
driver_headfull.get('http://a_certain_ip')
#set a datepicker
element_to_interact_with_headfull = driver_headfull.find_element_by_id('datesPolicyEffectiveDate')
driver_headfull.execute_script("arguments[0].value = '2020-07-01';", element_to_interact_with_headfull)
#set a <div> that behaves like a <select>.
element_to_interact_with_headfull = driver_headfull.find_element_by_id('industryDescription')
driver_headfull.execute_script("arguments[0].click();", element_to_interact_with_headfull)
element_pseudo_select_option_headfull = driver_headfull.find_element_by_id('descriptionIndustryoption0')
driver_headfull.execute_script("arguments[0].click();", element_pseudo_select_option_headfull)
# this very last instruction resets value of html_id=datesPolicyEffectiveDate (datepicker)
while(True):
pass
Any ideas will be so welcome!
Well, this was a pain. I'll post it in case it's of any use for someone.
It seems the component was reloaded, and I was setting the son of the component by means of
arguments[0].value = '2020-07-01';
so the parent wouldn't see the change, and would automatically reload the child with a default (empty) value.
Adding the following snippet solved my trouble:
driver_headfull.execute_script("arguments[0].value = '2021-07-01';", element_to_interact_with_headfull)
driver_headfull.execute_script("arguments[0].dispatchEvent(new Event('input', { bubbles: true }));", element_to_interact_with_headfull)

Looking for a timeout function in Python that will run on Windows

I've got a Python program on Windows 10 that runs a loop thousands of times with multiple functions that can sometimes hang and stop the program execution. Some are IO functions, and some are selenium webdriver functions. I'm trying to build a mechanism that will let me run a function, then kill that function after a specified number of seconds and try it again if that function didn't finish. If the function completes normally, let the program execution continue without waiting for the timeout to finish.
I've looked at at least a dozen different solutions, and can't find something that fits my requirements. Many require SIGNALS which is not available on Windows. Some spawn processes or threads which consume resources that can't easily be released, which is a problem when I'm going to run these functions thousands of times. Some work for very simple functions, but fail when a function makes a call to another function.
The situations this must work for:
Must run on Windows 10
A "driver.get" command for selenium webdriver to read a web page
A function that reads from or writes to a text file
A function that runs an external command (like checking my IP address or connecting to a VPN server)
I need to be able to specify a different timeout for each of these situations. A file write should take < 2 seconds, whereas a VPN server connection may take 20 seconds.
I've tried the following function libraries:
timeout-decorator 0.5.0
wrapt-timeout-decorator 1.3.1
func-timeout 4.3.5
Here is a trimmed version of my program that includes the functions I need to wrap in a timeout function:
import csv
import time
from datetime import date
from selenium import webdriver
import urllib.request
cities = []
total_cities = 0
city = ''
city_counter = 0
results = []
temp = ''
temp2 = 'IP address not found'
driver = None
if __name__ == '__main__':
#Read city list
with open('citylist.csv') as csvfile:
readCity = csv.reader(csvfile, delimiter='\n');
for row in csvfile:
city = row.replace('\n','')
cities.append(city.replace('"',''))
#Get my IP address
try:
temp = urllib.request.urlopen('http://checkip.dyndns.org')
temp = str(temp.read())
found = temp.find(':')
found2 = temp.find('<',found)
except:
pass
if (temp.find('IP Address:') > -1):
temp2 = temp[found+2:found2]
print(' IP: [',temp2,']\n',sep='')
total_cities = len(cities)
## Open browser for automation
try: driver.close()
except AttributeError: driver = None
options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-logging"])
driver = webdriver.Chrome(options=options)
#Search for links
while (city_counter < total_cities):
city = cities[city_counter]
searchTerm = 'https://www.bing.com/search?q=skate park ' + city
## Perform search using designated search term
driver.get(searchTerm)
haystack = driver.page_source
driver.get(searchTerm)
found = 0
found2 = 0
while (found > -1):
found = haystack.find('<a href=',found2)
found2 = haystack.find('"',found+10)
if (haystack[found+9:found+13] == 'http'):
results.append(haystack[found+9:found2])
city_counter += 1
driver.close()
counter = 0
while counter < len(results):
print(counter,': ',results[counter],sep='')
counter += 1
The citylist.csv file:
"Oakland, CA",
"San Francisco, CA",
"San Jose, CA"

Sending keystrokes to inactive window

Hello i am trying to make a app that sends a key press 2 chrome the current code i have is:
#you will need the win32 libraries for this snippet of code to work, Links below
import win32gui
import win32con
import win32api
from time import sleep
#[hwnd] No matter what people tell you, this is the handle meaning unique ID,
#["Notepad"] This is the application main/parent name, an easy way to check for examples is in Task Manager
#["test - Notepad"] This is the application sub/child name, an easy way to check for examples is in Task Manager clicking dropdown arrow
#hwndMain = win32gui.FindWindow("Notepad", "test - Notepad") this returns the main/parent Unique ID
hwndMain = win32gui.FindWindow("Notepad", "test - Notepad")
#["hwndMain"] this is the main/parent Unique ID used to get the sub/child Unique ID
#[win32con.GW_CHILD] I havent tested it full, but this DOES get a sub/child Unique ID, if there are multiple you'd have too loop through it, or look for other documention, or i may edit this at some point ;)
#hwndChild = win32gui.GetWindow(hwndMain, win32con.GW_CHILD) this returns the sub/child Unique ID
hwndChild = win32gui.GetWindow(hwndMain, win32con.GW_CHILD)
#print(hwndMain) #you can use this to see main/parent Unique ID
#print(hwndChild) #you can use this to see sub/child Unique ID
#While(True) Will always run and continue to run indefinitely
while(True):
#[hwndChild] this is the Unique ID of the sub/child application/proccess
#[win32con.WM_CHAR] This sets what PostMessage Expects for input theres KeyDown and KeyUp as well
#[0x44] hex code for D
#[0]No clue, good luck!
#temp = win32api.PostMessage(hwndChild, win32con.WM_CHAR, 0x44, 0) returns key sent
temp = win32api.PostMessage(hwndChild, win32con.WM_CHAR, 0x44, 0)
#print(temp) prints the returned value of temp, into the console
print(temp)
#sleep(1) this waits 1 second before looping through again
sleep(1)
but my issue is how i find chrome seing as it changes it name to the active tab and using this does not work either:
hwndMain = win32gui.FindWindow("Google Chrome", None)
in similar thread i saw this worked for people. and your code as is by changing title name works for me but you need to modify the title input order to this format.
hwndMain = win32gui.FindWindow( "Google Chrome" , None)
TO
hwndMain = win32gui.FindWindow( None , "Google Chrome")

How to use a webscraper running on an EC2 instance with lambda functions?

I have built a webscraper using python and selenium with geckodriver, it is currently running in an EC2 instance on a crontab schedule.
My issue is it takes more than 5 minutes to finish downloading and I want to use lamda functions to run my scraper but they only allow for 5 minutes of runtime.
So I have a code similar to this.
from selenium import webdriver
def start_browser(url):
browser = webdriver.Firefox( executable_path="./geckodriver")
executable_path="./geckodriver")
browser.get(url)
return browser
def log_in(user, pass, user_elem, pass_elem, login_elem, browser):
user_elem.click().send_keys(user)
pass_elem.click().send_keys(pass)
login_elem.click()
return browser
def nav_to_data(browser, data_elem)
data_elem.click()
return browser
def find_data(browser, data_table)
data_links = data_table.find_elements_by_tag_name("tr")
return data_links, browser
I'm thinking these functions could be ran on lambda functions passing the browser/webdriver instance to each other?
The part I'm struggling with is looping through the data and waiting for all downloads to finish, this would take longer than 5 mins.
Is there anyway around this?
def download_data(browser, link)
link.click()
time.sleep(2)
download_elem = browser.find_element_by_id("download_xls_file")
download_path = download_elem.click()
return download_path
# THIS TAKES LONGER THAN 5 mins
download_paths = []
for link in data_links:
download = download_data(browser, link) # clicks a link to a new page wdownload button and returns path to the .xls file
download_paths.append(download)
upload_data()
You can partition your data and use a recursive lambda to process chunks of your list.
Taking an example from my blog
def invoke_self_async(data_list, context):
this_data_list = data_list[0:20] # increase number as needed
new_event = {
'data': data_list[20:] # needs to match above number
}
boto3.client('lambda').invoke_async(
FunctionName=context.invoked_function_arn,
InvokeArgs=json.dumps(new_event)
)
my_data = []
for data in data_list:
download = download_data(browser, data) # returns path to .xls file
my_data.append(download)
return my_data

Problems with iterations and retrieving information with python3 and selenium

I am new to python and managed to write a little program (using python3) to retrieve information from a website. I have two problems:
I do not know how to tell python to wait each 80th step, so when i = 80, 160, 240 etc.
I do not know how to tell python to retrieve the information from the website how many steps exist in total (as this varies from page to page), see image below. I can see in the picture that the maximum amount of 260 is "hard-coded" in this example? Can I tell python to retrieve the 260 by itself (or any other number if this changes on another web page)?
How can I tell python to check which is the current page the script starts, so that it can adjust i to the page`s number? Normally I presume to start at page 0 (i = 0), but for example, if I were to start at page 30, my script shall be able to make i = 30 or if I start at 200, it shall be able to adjust i = 200 etc before it goes to the while loop.
Is it clear what I am troubling with?
This is the pseudo code:
import time
from selenium import webdriver
url = input('Please, enter url: ')
driver = webdriver.Firefox()
driver.get(url)
i = 0
while i > 260: # how to determine (book 1 = 260 / book 2 = 500)?
# do something
if i == 80: # each 80th page?
# pause
else:
# do something else
i = i + 1
else:
quit()
1) sleep
import time
....
if i % 80 == 0: # each 80th page?
# Wait for 5 seconds
time.sleep(5)
2) element selectors
html = driver.find_element_by_css_selector('afterInput').get_attribute('innerHTML')
3) arguments
import sys
....
currentPage = sys.argv[2]
or extract it from the source (see 2)
First, if you want to know if your i is "step"(devision) of 80 you can use the modulo sign, and check if it equal to 0, for instance:
if i % 80 == 0:
time.sleep(1) # One second
Second, you need to query the html you receive from the server, for instance:
from selenium import webdriver
url = input('Please, enter url: ')
driver = webdriver.Firefox()
driver.get(url)
total_pages = driver.find_element_by_css_selector('afterInput').get_attribute('innerHTML').split()[1] # Take only the number
after your edit: All you have to do is to is to assign i with this value you want by defining a variable in your script/parsing the arguments from the command line/scrape it from the website. This is Depends on your implementation and needs.
Other notes
I know you're on your beginning steps, but if you want to improve your code and make it a bit more pythonic I would do the following changes:
Using while and i = i + 1 is not a common pattern in python, instead use for i in range(total_pages) - of course you need to know the number of pages (from your second question)
There is no need to call quit(), your script will end anyway in the end of the file.
I think you meant while i < 260.

Categories