Vincent's home on the web
In my last job I was tasked to test extensively the User Interface (UI) of a new software under development and participate in the continuous development of the software.
The director did not know I was able to program, and so expected the task to take weeks to accomplish manually, due to the sheer amount of individual products to check.
After the first few hours, I worked out that I could write a program that would check every product and take screenshots of every option. It took me a day to write the first operational version, and then a few hours every time the software was modified. It took a bit less than 24 hours to verify all the products. I would have a report ready two days after and it would be used by the programmers team to make the next improvements.
It was written in Python using Selenium package. Here's an overview of the code:
import time
import pandas as pd
import os
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.firefox.options import Options
from datetime import datetime
wt = 30 # wait time if element not available
def click_main_menu():
WebDriverWait(driver, wt).until(
EC.presence_of_element_located((By.CSS_SELECTOR,
"div[id='vs-436c8801-3ba7-402f']"))).click()
def expand_main_menu():
for i in range(2): # for some reason i need to click main menu twice in order for its class status to update
click_main_menu()
time.sleep(1)
if check_if_main_menu_expanded() == False:
click_main_menu()
else:
pass
def expand_font_menu():
expand_main_menu()
time.sleep(2)
ul_ids = ['vs-78764df7-857b-44a9', 'vs-b4703944-3cd3-481b'] # sometimes another id is used
for ul_id in ul_ids:
try:
font_menu = driver.find_element_by_css_selector('ul[id='+ ul_id +']')
driver.execute_script("arguments[0].click();", font_menu)
break
except:
continue
def close_font_menu():
WebDriverWait(driver, wt).until(
EC.presence_of_element_located((By.CSS_SELECTOR,
"span[id='vs-98b7a99a-a727-4bce']"))).click()
def expand_color_menu():
expand_main_menu()
for color_menu_id in ['vs-cd66235b-3468-49fd','vs-696a1a04-f8e3-42ba']:
try:
WebDriverWait(driver, 5).until(
EC.element_to_be_clickable((By.CSS_SELECTOR,
"ul[id="+ color_menu_id +"]"))).click()
except:
continue
def close_color_menu():
WebDriverWait(driver, wt).until(
EC.presence_of_element_located((By.CSS_SELECTOR,
"span[id='vs-0b13c6ba-9318-4549']"))).click()
def close_image_menu():
try:
WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR,
"span[id='vs-89db7149-7486-46e1']"))).click()
except:
pass
def toggle_to_image(): # deprecated
WebDriverWait(driver, wt).until(
EC.presence_of_element_located((By.CSS_SELECTOR,
"span[id='vs-0ecceb6d-e883-424e']"))).click()
def toggle_image(lbl_id):
expand_main_menu()
img_lbl = WebDriverWait(driver, wt).until(
EC.presence_of_element_located((By.ID, lbl_id)))
img_span_slider = img_lbl.find_element_by_css_selector('span[class="slider round"]')
driver.execute_script("arguments[0].click();", img_span_slider) # solved "element not interactable problem
def toggle_name_and_dates(lbl_id):
expand_main_menu()
name_text_lbl = driver.find_element_by_id(lbl_id)
name_text_slider = name_text_lbl.find_element_by_css_selector('span[class="slider round"]')
driver.execute_script("arguments[0].click();", name_text_slider) # solved "element not interactable problem
def open_image_menu():
expand_main_menu()
span_ids = ['vs-59a37434-e24e-4cf9', 'vs-3fedd5bd-ccd6-45b3']
for span_id in span_ids:
try:
WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR,
"span[id="+ span_id +"]"))).click()
time.sleep(2)
break
except:
continue
def drag_and_screenshot(side, x, y, screenshot="no", go_back="no"):
action = ActionChains(driver)
action.click_and_hold(on_element=None)
action.move_by_offset(x, y)
action.release(on_element=None)
action.perform()
time.sleep(1)
if screenshot == "yes":
driver.save_screenshot(product_path + product[0] + "_" + side + ".png")
if go_back == "yes":
action = ActionChains(driver)
action.click_and_hold(on_element=None)
action.move_by_offset(-x, -y)
action.release(on_element=None)
action.perform()
time.sleep(1)
def check_if_main_menu_expanded():
menu = driver.find_element_by_css_selector("div[id='vs-436c8801-3ba7-402f']")
menu_class = menu.get_attribute("class")
if "collapsed" in menu_class:
return False
else:
return True
def collapse_main_menu():
for i in range(2): # for some reason i need to click main menu twice in order for its class status to update
click_main_menu()
time.sleep(1)
if check_if_main_menu_expanded() == True:
click_main_menu()
else:
pass
def get_surface_name():
surface_name = driver.find_element_by_id('vs-d85bfa6d-8df2-43b5')
return surface_name.text
def toggle_surface(direction):
if direction == "left":
WebDriverWait(driver, wt).until(
EC.presence_of_element_located((By.CSS_SELECTOR,
"span[id='vs-2b3a519e-2021-4c54']"))).click()
if direction == "right":
WebDriverWait(driver, wt).until(
EC.presence_of_element_located((By.CSS_SELECTOR,
"span[id='vs-0ecceb6d-e883-424e']"))).click()
def get_surface_list():
surface_list = [get_surface_name()]
for direction in ['right', 'left']:
for i in range(6):
try:
toggle_surface(direction)
surface = get_surface_name()
if surface not in surface_list:
surface_list.append(surface)
except:
pass
surface_list = list(set(surface_list))
return surface_list
def go_to_surface(surface):
temp_surface = get_surface_name()
if surface == temp_surface:
return None
for direction in ['right', 'left']:
try:
for i in range(6):
toggle_surface(direction)
temp_surface = get_surface_name()
if temp_surface == surface:
return None # exit loop
except:
pass
time.sleep(1)
def move_camera_to_surface(surface):
camera_dict = {"Front Surface":[0,0],"Top Surface": [0,150], "Back Surface": [330,0]}
if surface in camera_dict.keys():
x_y = camera_dict[surface]
drag_and_screenshot(surface, x_y[0], x_y[1])
def reinitialize_camera(surface):
camera_dict = {"Front Surface":[-0,-0], "Top Surface": [-0,-150], "Back Surface": [-330,-0]}
if surface in camera_dict.keys():
x_y = camera_dict[surface]
drag_and_screenshot(surface, x_y[0], x_y[1])
def capture_product_screenshots():
try:
collapse_main_menu()
driver.save_screenshot(product_path + product[0] + "_front.png")
drag_and_screenshot("left", 150, 0, screenshot="yes", go_back="yes")
drag_and_screenshot("top", 0, 150, screenshot="yes", go_back="yes")
drag_and_screenshot("back", 330, 0, screenshot="yes", go_back="yes")
drag_and_screenshot("right", 480, 0, screenshot="yes", go_back="yes")
except Exception as e: print(e)
def engraving_colors_validation():
try:
expand_color_menu()
if not os.path.exists(colors_path): os.makedirs(colors_path)
color_options = driver.find_element_by_css_selector("div[id='vs-eab5835d-e6c7-4ef0']")
color_options = color_options.find_elements_by_tag_name('a')
for color in color_options: # try every color and take a screenshot
color_text = color.text
# print(color_text)
attributes.append((product[0],color_text, 1))
color.click()
wait("engraving_colors_validation")
collapse_main_menu()
driver.save_screenshot(colors_path + product[0] + "_" + color_text + ".png")
expand_color_menu()
close_color_menu()
wait("engraving_colors_validation")
except Exception as e:
print(e)
attributes.append((product[0],"no_color_menu", 1))
def fonts_validation():
try:
expand_font_menu()
if not os.path.exists(fonts_path): os.makedirs(fonts_path)
font_options = driver.find_element_by_css_selector("div[id='vs-0d5121ef-a302-4e1f']")
font_options = font_options.find_elements_by_tag_name('a')
font_index = 0
for font in font_options:
# wait_time = 20 if font_index > 0 else 5
font_text = font.text
# print(font_text)
attributes.append((product[0],font_text, 1))
font.click()
# time.sleep(wait_time)
wait("fonts_validation")
collapse_main_menu()
driver.save_screenshot(fonts_path + product[0] + "_" + font_text + ".png")
expand_font_menu()
font_index += 1
close_font_menu()
# time.sleep(10)
wait("fonts_validation")
except Exception as e:
print(e)
attributes.append((product[0],"no_font_menu", 1))
def images_validation():
try:
if not os.path.exists(images_path): os.makedirs(images_path)
open_image_menu()
# get list of all images in menu and screenshot each
image_divs = driver.find_elements_by_css_selector("div[id*='vs-462b9c22-3c3e-4184']")
meca = 0
laser = 0
for image_div in image_divs: # get image code for each image and append to list
image_name = image_div.get_attribute('id')
image_name = image_name.split('_')
image_name = image_name[-1]
product_image_list.append((product[0], image_name))
if "M" in image_name: # detect if there were images with M
meca = 1
else:
laser = 1
def screenshot_image():
image_div.click()
# time.sleep(25)
wait("images_validation")
collapse_main_menu()
driver.save_screenshot(images_path + product[0] + "_" + image_name + ".png")
open_image_menu()
# if product_index % 10 == 0:
# screenshot_image()
# pass
if meca == 1:
attributes.append((product[0],"meca", 1))
if laser == 1:
attributes.append((product[0],"laser", 1))
close_image_menu()
# time.sleep(10)
wait("images_validation")
except Exception as e: print(e)
def enable_image_and_text(surface):
d = {"Top Surface":top_lbl_ids, "Back Surface":back_lbl_ids, "Front Surface":front_lbl_ids}
try:
print("toggling image")
toggle_image(d[surface]['image'])
# time.sleep(35)
wait("enable_image_and_text")
except Exception as e: print(e)
try:
print("toggling text")
toggle_name_and_dates(d[surface]['text'])
# time.sleep(35)
wait("enable_image_and_text")
except Exception as e: print(e)
def get_splashScreen_style():
splashScreen = driver.find_element_by_id('splashScreen')
splashScreen_style = splashScreen.get_attribute('style')
return splashScreen_style
def wait(func_name):
try:
time.sleep(3)
splashScreen_style = get_splashScreen_style()
secs = 5
while splashScreen_style == "":
time.sleep(1)
secs += 1
if secs > 45 :
print("splashScreen > 50secs, break during func:", func_name)
break
splashScreen_style = get_splashScreen_style()
time.sleep(2)
except Exception as e: print(e)
def enable_image(surface):
try:
if surface == "Top Surface":
toggle_image(top_lbl_ids['image'])
# time.sleep(25)
wait("enable_image")
if surface == "Back Surface":
toggle_image(back_lbl_ids['image'])
# time.sleep(25)
wait("enable_image")
if surface == "Front Surface":
toggle_image(front_lbl_ids['image'])
# time.sleep(25)
wait("enable_image")
except Exception as e:
print(e)
attributes.append((product[0],"no_image_menu", 1))
def check_text_fields():
try:
expand_main_menu()
for name_input_id in ['vs-da61663f-cda3-47ca', 'vs-29217229-5747-4857']:
try:
name_input = driver.find_element_by_id(name_input_id)
name_input.clear()
name_input.send_keys("Jean-Roch Martel de la Rochelière (44 chars)")
except:
continue
for mid_input_id in ['vs-41c7e01e-c6e9-4ad1','vs-30b26d11-89a8-402d']:
try:
mid_input = driver.find_element_by_id(mid_input_id)
mid_input.clear()
mid_input.send_keys("Un père dévoué à sa famille et ses proches (53 chars)")
except:
continue
for date_input_id in ['vs-6daa182a-6ab2-4db6','vs-58d18d04-34a5-4ace']:
try:
date_input = driver.find_element_by_id(date_input_id)
date_input.clear()
date_input.send_keys("1910 - 2010")
except:
continue
#submit
for submit_id in ['vs-65d999ee-c788-4889','vs-f2b30205-15b9-4051']:
try:
WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR,
"a[id=" + submit_id + "]"))).click()
except:
continue
# time.sleep(45)
wait("check_text_fields")
except Exception as e: print(e)
path = ".../Configurateur 3D/testing/"
df = pd.read_excel(path + "master_testing.xlsx",sheet_name="ronde 5",skiprows=3)
n_df = df[df['Repasser le robot'] == 1]
product_list = list(zip(n_df['code'], n_df['url']))
report_save_path = ".../Documents/temp_drive/"
base_url = "..."
attributes = []
product_image_list = []
product_index = 0
# products_done = [x for x in os.listdir(report_save_path) if ".xlsx" not in x] # products for which I have images
for product in product_list:
product_start = datetime.now()
url = product[1]
print(product[0], url)
attributes.append((product[0], "url", url))
options = Options()
options.binary_location = r'.../Mozilla Firefox/firefox.exe'
driver = webdriver.Firefox(options=options)
driver.set_window_size(1920, 1080) # for better screenshots
driver.get(url) # open product page
# time.sleep(10) # wait for page loading
wait("initial wait")
# find div id splashScreen where style="display: none;"
try:
splashScreen = driver.find_element_by_id('splashScreen')
splashScreen_style = splashScreen.get_attribute('style')
except:
print("could not get splashscreen, next product")
driver.close()
continue
if splashScreen_style == "": # page produit n'ouvre pas
attributes.append((product[0],"produit ouvre", 0))
print("produit n'ouvre pas")
driver.close()
continue
attributes.append((product[0],"produit ouvre", 1))
print("produit ouvre")
# paths
product_path = report_save_path + product[0] + "/"
colors_path = product_path + "couleurs gravure/"
fonts_path = product_path + "polices/"
images_path = product_path + "images/"
try: # check presence of menu
menu = driver.find_element_by_css_selector("div[id='vs-436c8801-3ba7-402f']")
attributes.append((product[0],"menu_disponible", 1))
except:
attributes.append((product[0],"menu_disponible", 0))
print("menu unavailable")
driver.close()
continue
# create product path
if not os.path.exists(product_path): os.makedirs(product_path)
# surfaces
surface_list = get_surface_list()
main_surface = "Front Surface" if len(surface_list) > 1 else surface_list[0]
front_lbl_ids = {'image':'vs-9a1e0552-7792-4055', 'text': 'vs-fd9f96da-6671-4f46'}
top_lbl_ids = {'image':'vs-72cb0d23-f728-4f10', 'text': 'vs-57d24b11-600c-4a43'}
back_lbl_ids = {'image':'vs-c3aeabee-02e6-40b0', 'text': 'vs-92fea04e-a6f9-4846'}
for surface in surface_list:
go_to_surface(surface)
print("current surface:", surface)
move_camera_to_surface(surface)
if surface == main_surface:
print("main surface, enable_image")
enable_image(surface)
print("engraving_colors_validation")
engraving_colors_validation()
print("fonts_validation")
fonts_validation()
print("images_validation")
images_validation()
print("check_text_fields")
check_text_fields()
else:
print("not main surface, enable_image_and_text")
enable_image_and_text(surface)
reinitialize_camera(surface)
print("capture_product_screenshots")
capture_product_screenshots()
driver.close()
product_index += 1
print("product done, it took:", datetime.now() - product_start)
def report():
now = datetime.now().strftime('%d-%m-%Y %H%M%S')
# img_df = pd.DataFrame(product_image_list, columns=['code', 'image'])
# img_df.to_excel(report_save_path + 'product_images'+ now +'.xlsx') # save product-image list in Excel
attr_df = pd.DataFrame(attributes, columns=['code', 'attr', 'value'])
pivot = attr_df.pivot(index="code", columns="attr", values="value")
pivot.to_excel(report_save_path + 'data'+ now +'.xlsx') # save product attributes in Excel
report()