1. The directory structure is as follows
2. main.py
import os import shut-off from playwright.sync_api import sync_playwright from config.setting import config from utils.template import Template from utils.md5 import Md5 from utils.delete import del_files import pytest from utils.dir_check import check_dir from utils.baseurl import get_baseUrl def run(): check_dir() data = os.listdir('data') m = Md5('case', 'log', 'case_md5.json') n = Md5('utils', 'log', 'template_md5.json') filter_list = m. filter() utils_list = n. filter() if 'template.py' not in utils_list: filter_list = [] n.write_md5() for i in data: file_path = 'data' + '/' + i if os.path.isfile(file_path): temp = 'test_' + i if temp not in filter_list: Template.create_test_file(file_path, 'case') m.write_md5() if __name__ == "__main__": run() del_files('results') pytest.main(['-s', '--alluredir=results']) os.system('allure generate --clean ./results/ -o ./report/') for file_name in os.listdir('resource'): src_file = os.path.join('resource', file_name) dst_file = os.path.join('report', file_name) if os.path.exists(dst_file): os. remove(dst_file) shutil. copy(src_file, 'report') os.system('allure open -h 127.0.0.1 -p 8883 ./report/')
3. conftest.py
import pytest from playwright.sync_api import sync_playwright from config.setting import config from playwright.sync_api import Page from utils.operate import operate from utils.baseurl import get_baseUrl import os import allure from utils.video import generate_video @pytest.fixture(scope='session') def page(): browser = sync_playwright().start().chromium.launch(headless=False, slow_mo=500) page = browser.new_page(ignore_https_errors=True, record_video_dir='temp') page.goto(get_baseUrl(config)) operate(config['username'], page) operate(config['password'], page) operate(config['submit'], page) return page def log(request): with open('log/http.txt', 'a', encoding='utf-8') as w: w.write(f'{request}.url' + '\\ ') @pytest.fixture(scope='function', autouse=True) def after(page: Page): yield page.on("request", lambda request: log(request)) @pytest.fixture(scope='session', autouse=True) def clear(page: Page): yield # page. close() p = generate_video('temp', 'video') allure.attach.file(p, f'{os.path.basename(p)}', attachment_type=allure.attachment_type.WEBM, extension='WEBM')
4. Case directory, content and directory are automatically generated
5. config directory, save the configuration
dir_collection.py
The directories in the configuration are automatically generated
dir_collections = [ 'case', 'log', 'img', 'video', 'temp' ]
env.py
Environment variable configuration
env = { 'prod': '', 'dev': '', 'test': 'http://test.lan' }
setting.py
config = { 'baseUrl': '', 'url': '/user/login', 'username': { 'selector': '#userName', 'type': 'input', 'value': 'test' }, 'password': { 'selector': '#password', 'type': 'input', 'value': '123' }, 'submit': { 'selector': '#root > div > div > div:nth-child(1) > div > form > div:nth-child(3) > button', 'type': 'button' }, }
6. Data directory
The test file in the case is automatically generated based on the data in the data
homepage.py
homepage_cfg = [ { 'name': 'homepage', 'url': '', 'step': [ ], 'assert': [ { 'selector': '#content > div > div > div > div > div.react-grid-layout.layout > div:nth-child(1) > div ' '> div > div._3A9TZ-vnPrcf2IwqBmUPoX', 'value': 'Number of violation warnings' }, { 'selector': '#content > div > div > div > div > div.react-grid-layout.layout > div:nth-child(2) > div ' '> div > div._3A9TZ-vnPrcf2IwqBmUPoX', 'value': 'Confirm the number of violation alarms 1' }, { 'selector': '#content > div > div > div > div > div.react-grid-layout.layout > div:nth-child(3) > div ' '> div > div._3A9TZ-vnPrcf2IwqBmUPoX', 'value': 'Number of unconfirmed violation alerts' }, ] } ]
keyword.py
keyword_cfg = [ { 'name': 'keyword', 'url': '/keyword/info', 'step': [ { "type": 'input', "selector": 'text=keyword group name', "value": 'UI Test' }, { "type": 'input', "selector": 'text=keyword group description', "value": 'UI new keywords' }, ], 'assert': [ { 'selector': '#content > div > div > div > h3', 'value': 'New keyword strategy' }, { 'selector': '#content > div > div > div > div > div > div > div > form > div:nth-child(1) > div.ant-form-item-label > label', 'value': 'keyword group name' }, { 'selector': '#content > div > div > div > div > div > div > div > form > div:nth-child(2) > div.ant-form-item-label > label', 'value': 'keyword phrase description' }, ] } ]
7. img directory, the directory for saving error screenshots, automatically generated
8, log directory, save the request log and two md5 files, these two md5 files are mainly used to identify whether to regenerate the test files in the case directory for each run
9. Report directory, automatically generated by allure command
10. The resource directory, because a small amount of modification has been made to the report of allure, so the resource directory needs to be kept. When the report is generated, the contents in the resource directory and the contents in the report will be replaced
11. The results directory, generated by the allure command, saves the test result data
12. The temp directory will automatically generate a temporary directory. The recorded video files will be saved to temp, and then the video will be renamed and saved to the video directory. Temp will be automatically generated before each run, and will be automatically deleted after running
13, utils directory, the directory for storing encapsulation methods
add_style.py
from playwright.sync_api import Page def add_style(page: Page, elements, flag: int): if flag == 0: script = f"document.querySelector('{elements}').setAttribute('style','border-style: solid " \ f";border-color:green')" else: script = f"document.querySelector('{elements}').setAttribute('style','border-style: solid " \ f";border-color:red') " page. evaluate(script)
assert_element.py
from typing import List from playwright.sync_api import Page from utils.add_style import add_style from utils.screenshot import error_screenshot def assert_element(arr: List, page: Page): li = [] if arr: for i in arr: if page.query_selector(i['selector']): text = page.query_selector(i['selector']).inner_text() if text == i['value']: add_style(page, i['selector'], 0) pass else: add_style(page, i['selector'], 1) li.append(i['value']) else: li.append(i['selector']) if li: error_screenshot(page, 'img') raise AssertionError(f"some elements in {str(li)} isn't matched or exists")
baseurl.py
from typing import Dict from config.env import env from utils.params_error import ParamsError def get_baseUrl(conf: Dict): import sys if len(sys.argv) > 1: if sys.argv[1] == 'dev': conf['baseUrl'] = env['dev'] elif sys.argv[1] == 'prod': conf['baseUrl'] = env['prod'] elif sys.argv[1] == 'test': conf['baseUrl'] = env['test'] else: raise ParamsError('python main.py [test]|[prod]|[dev]') else: raise ParamsError('python main.py [test]|[prod]|[dev]') url = conf['baseUrl'] + conf['url'] return url
delete.py
import os def del_files(dir_path: str): if os.path.exists(dir_path): for filename in os.listdir(dir_path): filepath = os.path.join(dir_path, filename) try: if os.path.isfile(filepath): os. unlink(filepath) except Exception as e: print(f"Error deleting {filepath}: {e}")
dir_check.py
import os from config.dir_collection import dir_collections def check_dir(): li = os.listdir() for i in dir_collections: if i not in li: os.mkdir(i)
md5.py
import hashlib import json import os from json import JSONDecodeError class Md5: def __init__(self, dir_path, md5_path, file_name): self.dir_path = dir_path # directory path self.md5_path = md5_path # MD5 file path self.file_name = file_name # MD5 file name file_path = os.path.join(md5_path, file_name) if not os.path.exists(file_path): open(file_path, mode='w + ', encoding='utf-8').close() # If the MD5 file does not exist, create the file def generate_md5(self): temp = {} # If dir_path is a file instead of a directory, throw an IOError exception if os.path.isfile(self.dir_path): raise IOError(f'Message: parameter <dir_path:{self.dir_path}> must be directory') else: dir_list = os.listdir(self.dir_path) # Get the list of files in the directory if len(dir_list) != 0: for i in dir_list: md5 = hashlib.md5() # create MD5 object file_path = os.path.join(self.dir_path, i) # get file path if os.path.isfile(file_path) and os.path.basename(file_path).endswith('.py'): # If it is a file with open(file_path, mode='r', encoding='utf-8') as f: md5.update(f.read().encode(encoding='utf-8')) # update MD5 value hex_md5 = md5.hexdigest() # Get MD5 value temp[i] = hex_md5 # add the filename and MD5 value to the dictionary return temp # return dictionary def write_md5(self): file_path = os.path.join(self.md5_path, self.file_name) # Write the dictionary generated by generate_md5() to the file json.dump(self.generate_md5(), open(file_path, mode='w + ', encoding='utf-8')) def read_md5(self): file_path = os.path.join(self.md5_path, self.file_name) try: with open(file_path, mode='r', encoding='utf-8') as f: # Read the json data in the file and return return json. load(f) except JSONDecodeError: # If the json data in the file fails to parse, return an empty dictionary return {} def filter(self): old_md5 = self.read_md5() # Get the old MD5 value new_md5 = self.generate_md5() # Get new MD5 value # Return a list of filenames with the same old and new md5 values return [k for k, v in new_md5.items() if k in old_md5 and v == old_md5[k]]
operate.py
from playwright.sync_api import Page def operate(d: dict, page: Page): if d.get('type') == 'input': page.query_selector(d.get('selector')).fill(d.get('value')) elif d.get('type') == 'button': page.query_selector(d.get('selector')).click()
params_error.py
class ParamsError(Exception): def __init__(self, msg: str): super(ParamsError, self).__init__(msg)
parse.py
from playwright.sync_api import Page from config.setting import config def parse(conf: dict, page: Page): url = config['baseUrl'] + conf['url'] if url != '': page. goto(url) if conf['step']: for i in conf['step']: if i.get('type') == 'input': page.query_selector(i.get('selector')).fill(i.get('value')) elif i.get('type') == 'button': page.query_selector(i.get('selector')).click()
screenshot.py
import time import allure from playwright.sync_api import Page def error_screenshot(page: Page, path: str): file_path = f'{path}/{int(time.time())}.png' page.screenshot(path=file_path, type='png', full_page=True) allure.attach.file(file_path, f'{path}/{int(time.time())}', attachment_type=allure.attachment_type.PNG, extension='PNG')
template.py
import os class Template: @staticmethod def check_todo_file(file_path: str) -> bool: """ Check if the file content contains '# TODO' string Args: file_path (str): file path Returns: bool: Returns True if contains '# TODO' string, otherwise returns False """ with open(file_path, mode='r + ', encoding='utf-8') as file: return '# TODO' in file.read() @staticmethod def create_test_file(file_path: str, target_path: str) -> None: """ Create test file Args: file_path (str): file path target_path (str): target path """ if Template.check_todo_file(file_path): print(f'Message: <TODO> tag found, file <{file_path}> not yet completed') return file_name = os.path.basename(file_path).replace('.py', '') import_name = f'{file_name}_cfg' test_file_path = os.path.join(target_path, f'test_{file_name}.py') with open(test_file_path, mode='w + ', encoding='utf-8') as file: file.write(f'''import pytest import allure from data.{file_name} import {import_name} from playwright.sync_api import Page from utils.parse import parse from utils.assert_element import assert_element @allure.suite('{file_name}') class Test_{file_name. capitalize()}: @allure.sub_suite('{import_name}') @pytest.mark.parametrize('cfg', {import_name}) def test_{file_name}(self, cfg, page): parse(cfg, page) allure.dynamic.title(cfg['name']) assert_element(cfg['assert'], page) ''') print(f"Message: File <{test_file_path}> created successfully")
video.py
import os import time # def remove_video(path: str): # print(os. listdir(path)) # if os.listdir(path): # for i in os.listdir(path): # os. remove(f'{path}/{i}') def generate_video(source_path: str, target_path: str): p = f"{target_path}/{int(time.time())}.webm" while True: if os.listdir(source_path): for i in os.listdir(source_path): os.renames(f'{source_path}/{i}', p) break return p
14. The video directory is automatically generated to store the recorded video directory
15. Reporting effects
The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledgePython entry skill treeHomepageOverview 298962 people are studying systematically