Пример теста на Robot Framework с pywinauto
Введение
Перед изучением этой статьи убедитесь, что вы владете материалом, изложенном в статье «Архитектура тестов на Robot Framework»
Пример
Рассмотрим пример тестирования Desktop приложения, которое создаёт
базы данных
PostgreSQL
.
Для работы с
PostgreSQL
нам понадобится библиотека
psycopg2
, для контроля приложения используем
PyWinAuto
robot |-- __init__.py |-- libraries | |-- __init__.py | |-- config.py | |-- fixtures.py | |-- pg_utils.py | |-- app.py | `-- utils.py |-- resources | `-- Fixtures.robot |-- tests `-- test_new_db.robot
test_new_db.robot
*** Settings *** Documentation Tests that new database can be created with unique name Resource ../resources/fixtures.robot Library ../libraries/app.py Test Setup Start TestCase Test Teardown Finish TestCase *** Variables *** *** Test Cases *** Impossible To Create Database With Existing Name ${app} = App Tool Click Button ${app} Detect path Click Button ${app} Save Click Button ${app} New ${db_name} = Random Existing Db Name Log ${db_name} Type New Db Name ${app} ${db_name} Click Button ${app} OK ${title_to_verify} = Db Exists Title ${db_name} ${impossible} = Window Title Exists ${app} ${title_to_verify} Should Be True ${impossible}
Fixtures.robot
*** Settings *** Documentation Test Setup and Test Teardown Library ../libraries/fixtures.py *** Keywords *** Start TestCase Setup AppTool Finish TestCase Teardown AppTool
config.py
import os import configparser def credentials(filename="C:\sandbox\pywinauto\database_dos.toml", section="postgresql"): print("config.credentials()") try: os.path.isfile(filename) print(f"file {filename} is found") except FileNotFoundError as e: print(e) # create a parser config = configparser.ConfigParser() # read config file print(config.read(filename)) # get section, default to postgresql db = {} if config.has_section(section): params = config.items(section) for param in params: db[param[0]] = param[1] else: raise Exception(f"Section {section} not found in the {filename} file") return db
fixtures.py
import os import utils # App Tool def setup_AppTool(usecase="default", lang="en"): """ Copies default AppTool.prm file to settings directory Closes opened AppTool instance """ print("AppTool TEST SETUP STARTED") cur_dir = os.getcwd() print(f"Current Directory {cur_dir}") utils.close_software_instance("AppTool") utils.restore_settings("App Tool", usecase, lang) # utils.run_tested_app("AppTool") print("AppTool TEST SETUP FINISHED") def teardown_AppTool(sqlite_db_path=False): print("AppTool TEST TEARDOWN STARTED") utils.close_software_instance("AppTool") if sqlite_db_path: utils.delete_file(sqlite_db_path) print("AppTool TEST TEARDOWN FINISHED")
pg_utils.py
#!/usr/bin/python import psycopg2 from robot.api import logger from config import credentials """ mode = "robot" if mode == "robot": print = log_to_console() def log_to_console(arg): logger.console('Message only to console.') """ def connect(): """ Connect to the PostgreSQL database server """ conn = None try: # read connection parameters params = credentials() for p in params: print(f"param: {p}") # connect to the PostgreSQL server print('Connecting to the PostgreSQL database...') conn = psycopg2.connect(**params) # create a cursor cur = conn.cursor() # execute a statement print('PostgreSQL database version:') cur.execute('SELECT version()') # display the PostgreSQL database server version db_version = cur.fetchone() print(db_version) return cur except (Exception, psycopg2.DatabaseError) as error: print(f"pg_utils.connect() error: {error}") def get_list_of_databases(): print("get_list_of_databases()") cur = connect() cur.execute("SELECT datname FROM pg_catalog.pg_database") databases = cur.fetchall() db_list = [db[0] for db in databases] cur.close() return db_list
app.py
import random import time from pywinauto.application import Application import utils import pg_utils as pg def db_exists_title(db_name: str) -> str: return f"Database {db_name} already exists!" def app_tool() -> object: app = Application(backend='uia').start(r"C:\Program Files\AredelApp\AppTool.exe") app = Application(backend='uia').connect(title='App Tool', timeout=100) return app def click_button(app: str, button: str): # print(f"app: {app}") # print(f"button: {button}") match button: case "Detect path": detect_path = app.AppTool.child_window( title="Detect path", control_type="Text" ).wrapper_object() detect_path.click_input() case "Save": save = app.AppTool.child_window( title="Save", control_type="Text" ).wrapper_object() save.click_input() case "New": if app.AppTool.NewButton.child_window( title="New", control_type="Text" ).exists(timeout=10): # time.sleep(1) new = app.AppTool.NewButton.child_window( title="New", control_type="Text" ).wrapper_object() print(f"'New' button is visible: {new.is_visible()}") # time.sleep(1) new.click_input() case "OK": ok = app.AppTool.Newdatabase.child_window( title="OK", control_type="Text" ).wrapper_object() ok.click_input() case "Close": close = app.AppTool.child_window(title="Close", control_type="Text") close.click_input() def type_new_db_name(app, db_name: str): if app.AppTool.Newdatabase.child_window( title="Name:", control_type="Text" ).exists(timeout=10): new_database = app.AppTool.Newdatabase.child_window( title="Name:", control_type="Text" ).wrapper_object() # new_database = app.AppTool.child_window(title="Name:", control_type="Text").wrapper_object() print(f"Tries to enter new db name: {db_name}") new_database.type_keys(db_name) def window_title_exists(app, title_to_check) -> bool: title_exists = app.AppTool.child_window( title=title_to_check, control_type="Text" ).exists(timeout=5) return title_exists def random_existing_db_name() -> str: print("random_existing_db_name()") existing_dbs = pg.get_list_of_databases() print(existing_dbs) i = random.randrange(len(existing_dbs)) db = existing_dbs[i] print(f"Database {db} was randomly choosen out of existing databases") print("/random_existing_db_name()") return db
utils.py
import os import uuid import shutil import psutil def close_software_instance(app_name: str): print(f"close_software_instance(): {app_name}") if app_name in ["TB", "Top Bicycle", "TopBicycle"]: app_name = "TopBicycle.exe" elif app_name in ["AT", "Aredel Tool", "AredelTool"]: app_name = "AredelTool.exe" else: print(f"close_software_instance(): Software is not from the known app list\ using it's name as it is: {app_name:-^140}") for proc in psutil.process_iter(['pid', 'name']): if proc.info["name"] == app_name: print(proc.info["pid"]) p = psutil.Process(proc.pid) p.terminate() print(f"/close_software_instance(): {app_name}") def delete_file(file: str): print(f"delete_file({file})") if (os.path.exists(file)): os.remove(file) print(f"File: {file} is deleted.") else: print(f"File: {file} does not exist.") print("/delete_file()") def delete_dir_content(path: str): for file in os.listdir(path): file_path = os.path.join(path, file) try: if os.path.isfile(file_path) or os.path.islink(file_path): os.unlink(file_path) elif os.path.isdir(file_path): shutil.rmtree(file_path) except Exception as e: print(f"Failed to delete {file_path}. Reason: {e}") def restore_settings(software: str, usecase="default", lang="en"): print(f"restore_settings() for {software:-^140}") rs_base = "C:\ProgramData\Aredel\AppPackage\" prm_base = "C:\AutoTests\pywinauto\prm_files\" if software in ["TP", "Top Bicycle", "TopBicycle"]: source_prm = prm_base + "result_viewer\" + usecase + "\" + lang + "\TopBicycle.prm" target_path = rs_base + "TopBicycle.prm" elif software in ["AT", "Aredel Tool", "AredelTool"]: print("AredelTool uses different setting system - not .prm files") current_user = os.getlogin() print(f"current user is {current_user}") settings_path = "C:\Users\" + current_user + "\AppData\Local\AredelTool" delete_dir_content(settings_path) source_prm = prm_base + "aredel_tool\default\en\Foo.prm" target_path = prm_base + "aredel_tool\default\en\Bar.prm" else: print(f"utils.restore_settings(): Software is not specified\ properly to restore settings for usecase: {usecase:-^140}") print(f"Rewriting {target_path} with {source_prm:-^140}") shutil.copyfile(source_prm, target_path) print(f"\restore_settings() for {software:-^140}")
Автор статьи: Андрей Олегович