Making a reservation for the driving license knowledge test can be difficult during summer times due to large demands. So I made this python script to monitor and catch available appointments and make automatic reservations.
The script is far from complete. But while I was still testing its functionalities and debugging some issues, it grabbed me a very nice appointment. LOL
Improvements needed:
- Add docstrings to the functions.
- Break the long workflow into functions.
- Figure out the bugs that could possibly mess up the workflow.
- Wonder if it’s possible to get it done without using Selenium, which is not efficient.
# Python3
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from bs4 import BeautifulSoup
from selenium.webdriver.common.action_chains import ActionChains
import time
import datetime
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# define config dicts and functions
loc_id_dict = {
'point_grey': "542161df66423009309050c01dcc17f7d1db1ef8e7cea3012331509b79ea6959",
'east_van': "0ab916058f4b572eae9dfbdf0693fa9f2f97a34a19bee6c68d09cb28b78ac3c3",
'guildford_surrey': "050a91560b08e8aedfef609cb69e0c41469b56e575f325681f98196495169661",
'metrotown_burnaby': "e879cd70e75ba8db2fb03b3d2060bf7c1c74e5d879ebea3cc585fd2d707a278d",
'north_van': "80a96ca1218413463f6601d41d2e97391e2b9da1aadaf033220be663264db0f3",
'richmond': "b1d4daefba2458c80d880e3931798ef508477d19f6cf1652327459a4a1a27ee3",
'royal_centre': "ea01f5e5ba07af767a739c1d66730bef9663a1a307b84e4674cffcd93caad1b5",
'surrey': "d8225a23dd9830e9684fb00f8aea2fff279c892cb1065244aaa0ae05396a0fe2",
}
def change_month(driver, direction="next"):
if direction=='next':
button = driver.find_elements_by_xpath("//*[contains(text(), 'chevron_right')]")[0]
else:
button = driver.find_elements_by_xpath("//*[contains(text(), 'chevron_left')]")[0]
button.click()
return
def check_av(driver):
pg = BeautifulSoup(driver.page_source)
available_button = "v-btn v-btn--flat v-btn--floating theme--light"
av_bt_list = pg.findAll("button", {"class": available_button})
return av_bt_list
def fill_info(driver):
driver.find_element_by_id('LastName').send_keys('Zhang')
driver.find_element_by_id('FirstName').send_keys('Haipeng')
driver.find_element_by_id('DOB').send_keys('my_birthday')
driver.find_element_by_id('Email').send_keys('my_email')
driver.find_element_by_id('ConfirmEmail').send_keys('my_email')
driver.find_element_by_id('Phone').send_keys('my_phone')
driver.find_element_by_xpath("//input[@type='checkbox']").click()
driver.find_element_by_id('contactStepCreateAppointmentButton').click()
return
def month_calendar(driver, month_range={'august':8, 'september':9, 'october':10}):
pg = BeautifulSoup(driver.page_source)
for x in month_range.keys():
if x in str.lower(pg.text):
return month_range[x]
# main function for the workflow
def search_and_book(loc_id_dict, current_month, current_day, current_loc):
driver = webdriver.Chrome(executable_path=r'/Users/roc/Documents/04-software_dev/02_icbc_reservation/chromedriver')
url = "https://onlinebusiness.icbc.com/qmaticwebbooking/#/"
driver.get(url)
t = 300 # max wait time
WebDriverWait(driver, t).until(
EC.element_to_be_clickable((By.CLASS_NAME, "v-input--selection-controls__ripple"))
).click()
time.sleep(0.5)
# work-around to select `Single knowledge test`
actions = ActionChains(driver)
actions.send_keys(Keys.TAB)
actions.send_keys(Keys.ARROW_DOWN)
actions.perform()
time.sleep(0.5)
earliest_day = 1000
new_finding = 0
month = 8
while new_finding == 0:
for location in loc_id_dict.keys():
# slecte a location
driver.find_element_by_id('step2').click()
WebDriverWait(driver, t).until(
EC.element_to_be_clickable((By.ID, loc_id_dict[location]))
).click()
av_bt_list = check_av(driver)
while (len(av_bt_list) == 0) & (month_calendar(driver) < 11):
time.sleep(0.5)
WebDriverWait(driver, t).until(
EC.element_to_be_clickable((By.XPATH, "//button[normalize-space()='chevron_right']"))
).click()
av_bt_list = check_av(driver)
if len(av_bt_list)>0:
earliest_day = str(str(av_bt_list[0]).split(">")[2]).split("<")[0]
if ((month_calendar(driver)+1)*100 + int(earliest_day)) > 813:
if ((month_calendar(driver)+1)*100 + int(earliest_day)) < (current_month*100 + current_day):
new_finding = 1
current_loc = location
current_month = (month_calendar(driver)+1)
current_day = int(earliest_day)
now = datetime.datetime.now()
print (now.strftime("%Y-%m-%d %H:%M:%S"))
print("Earlier date found! {}: {}.{}".format(current_loc, current_month, current_day))
n = 1
while n < 10:
while month_calendar(driver) > 8:
WebDriverWait(driver, t).until(
EC.element_to_be_clickable((By.XPATH, "//button[normalize-space()='chevron_left']"))
).click()
n+=1
assert month_calendar(driver) == 8
# if catching ealier choice: make a new reservation
if new_finding == 1:
new_finding = 0
print()
print("Making a new reservation...")
driver2 = webdriver.Chrome(executable_path=r'/Users/roc/Documents/04-software_dev/02_icbc_reservation/chromedriver')
url = "https://onlinebusiness.icbc.com/qmaticwebbooking/#/"
driver2.get(url)
t = 300 # max wait time
WebDriverWait(driver2, t).until(
EC.element_to_be_clickable((By.CLASS_NAME, "v-input--selection-controls__ripple"))
).click()
time.sleep(0.5)
# work-around to select `Single knowledge test`
actions = ActionChains(driver2)
actions.send_keys(Keys.TAB)
actions.send_keys(Keys.ARROW_DOWN)
actions.perform()
time.sleep(0.5)
WebDriverWait(driver2, t).until(
EC.element_to_be_clickable((By.ID, 'step2'))
).click()
WebDriverWait(driver2, t).until(
EC.element_to_be_clickable((By.ID, loc_id_dict[current_loc]))
).click()
av_bt_list = check_av(driver2)
while len(av_bt_list) == 0:
WebDriverWait(driver2, t).until(
EC.element_to_be_clickable((By.XPATH, "//button[normalize-space()='chevron_right']"))
).click()
av_bt_list = check_av(driver2)
# click the earliest date
earliest_day = str(str(av_bt_list[0]).split(">")[2]).split("<")[0]
xpath = "//button[normalize-space()={}]".format(earliest_day)
WebDriverWait(driver2, t).until(
EC.element_to_be_clickable((By.XPATH, xpath))
).click()
# click the earliest time
WebDriverWait(driver2, t).until(
EC.element_to_be_clickable((By.ID, "timeButton1"))
).click()
# fill in required information
time.sleep(2)
fill_info(driver2)
WebDriverWait(driver2, t).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "[aria-label='Cancel appointment.']"))
).click()
WebDriverWait(driver2, t).until(
EC.element_to_be_clickable((By.XPATH, "//button[normalize-space()='Yes']"))
).click()
# confirm new booking
time.sleep(1)
WebDriverWait(driver2, t).until(
EC.element_to_be_clickable((By.XPATH, "//button[normalize-space()='Book your appointment']"))
).click()
time.sleep(10)
print("New reservation made!!\n{}: {}.{}".format(current_loc, current_month, current_day))
driver2.quit()
driver.quit()
return [current_loc, current_month, current_day]
current_month = 8
current_day = 17
# earliest_month = 100
earliest_day = 1000
month = 8
earliest_location = "None"
current_loc = 'surrey'
new_finding = 0
try:
[current_loc, current_month, current_day] = search_and_book(loc_id_dict, current_month, current_day, current_loc)
except Exception as e:
print(e)