L'espace de Vincent sur le web
Dans mon dernier emploi, j'ai été chargé de tester exhaustivement l'interface utilisateur d'un nouveau logiciel en développement et de participer à son développement.
Le directeur n'était pas au courant que je pouvais programmer, et donc s'attendait que ça me prenne des semaines à compléter la tâche manuellement à cause du grand nombre de produits invidivuels à vérifier.
Après quelques heures, j'ai réalisé que je pourrais écrire un programme qui ferait la vérification de chaque produit et prendrait les captures d'écran de chaque option et variante. Ça m'a pris une journée à programmer la première version opérationnelle, et ensuite quelques heures de mises à jour chaque fois que le logiciel était amélioré. Le programme prenait un peu moins d'une journée à passer tous les produis en revue. Je pouvais le partir le soir et il était prêt pour analyse le sur-lendemain. Le rapport servait pour la prochaine ronde d'améliorations à l'équipe de programmeurs.
Le programme a été programmé avec le package Selenium de Python. Voici un aperçu du 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()