Pytest+selenium UI自动化测试实战实例
今天来说说pytest吧,经过几周的时间学习,有收获也有疑惑,总之最后还是搞个小项目出来证明自己的努力不没有白费。
环境准备
1 确保您已经安装了python3.x
2 配置python3+pycharm+selenium2
开发环境
3 安装pytest库pip install pytest
4 安装pytest -html 报告插件pip install pytest-html
5 安装pypiwin32库(用来模拟按键)pip install pypiwin32
6 安装openpyxl解析excel文件库pip install openpyxl
7 安装yagmail发送报告库pip install yagmail
8 确保已配置火狐或谷歌浏览器及对应驱动
9 确保已经正确配置好发送邮件的邮箱
项目简介
测试地址
https://mail.126.com
测试范围
1.126电子邮箱登录功能测试-验证正确帐号密码登录成功-验证错误用户名密码登录失败(有很多情况,用例里面做了充分的校验)
2.126电子邮箱添加联系人功能测试-验证正确填写必填项数据添加联系人成功-验证缺省必填项数据添加联系人失败-验证必填项字段数据格式错误添加联系人失败
3.126电子邮箱发送邮件功能测试-验证普通邮件发送成功-验证带附件邮件发送成功
项目设计
1.python编程语言设计测试脚本
2.webdriver驱动浏览器并操作页面元素
3.二次封装webdriver Api 操作方法
4.采用PageObject设计模式,设计测试业务流程
5.通过UI对象库存储页面操作元素
6.通过数据文件存储数据,读取数据,参数化测试用例并驱动测试执行
7.通过第三方插件pytest-html生成测试报告
8.通过yagmail第三方库,编写发送报告接口,测试工作完成后自动发送测试报告
目录结构
1 PytestAutoTestFrameWork2 |—|config3 |——|__init__.py4 |——|conf.py5 |——|config.ini6 |—|data7 |——|__init__.py8 |——|tcData.xlsx9 |—Page
10 |——|PageObject.py
11 |———|__init__.py
12 |———|ContactPage.py
13 |———|HomePage.py
14 |———|LoginPage.py
15 |———|SendMailPage.py
16 |——|__init__.py
17 |——|BasePage.py
18 |—|report
19 |—|TestCases
20 |——|__init__.py
21 |——|conftest.py
22 |——|test_confactCase.py
23 |——|test_loginCase.py
24 |——|test_sendMailCase.py
25 |—|util
26 |——|__init__.py
27 |——|clipboard.py
28 |——|keyboard.py
29 |——|parseConFile.py
30 |——|parseExcelFile.py
31 |——|sendMailForReport.py
32 |—|conftest.py
33 |—|pytest.ini
34 |—|RunTestCase.py
代码实现
通过126邮箱测试范围分析,我们需要通过设计剪切板,模拟键盘完成附件上传操作,因此我们首先来编写这两个方法
clipboard.py-操作剪切板
1 """2 ------------------------------------3 @Time : 2019/4/15 12:044 @Auth : linux超5 @File : clipboard.py6 @IDE : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 import win32con
11 import win32clipboard as WC
12
13
14 class ClipBoard(object):
15 '''设置剪切板内容和获取剪切板内容'''
16
17 @staticmethod
18 def getText():
19 '''获取剪切板的内容'''
20 WC.OpenClipboard()
21 value = WC.GetClipboardData(win32con.CF_TEXT)
22 WC.CloseClipboard()
23 return value
24
25 @staticmethod
26 def setText(value):
27 '''设置剪切板的内容'''
28 WC.OpenClipboard()
29 WC.EmptyClipboard()
30 WC.SetClipboardData(win32con.CF_UNICODETEXT, value)
31 WC.CloseClipboard()
32
33
34 if __name__ == '__main__':
35 from selenium import webdriver
36
37 value = 'python'
38 driver = webdriver.Firefox()
39 driver.get('http://www.baidu.com')
40 query = driver.find_element_by_id('kw')
41 ClipBoard.setText(value)
42 clValue = ClipBoard.getText()
43 query.send_keys(clValue.decode('utf-8'))
keyboard.py-模拟键盘
1 """2 ------------------------------------3 @Time : 2019/4/15 12:054 @Auth : linux超5 @File : keyboard.py6 @IDE : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10
11 # 模拟按键
12 import win32api
13 import win32con
14 import time
15
16
17 class KeyBoard(object):
18 """模拟按键"""
19 # 键盘码
20 vk_code = {
21 'enter' : 0x0D,
22 'tab' : 0x09,
23 'ctrl' : 0x11,
24 'v' : 0x56,
25 'a' : 0x41,
26 'x' : 0x58
27 }
28
29 @staticmethod
30 def keyDown(key_name):
31 """按下键"""
32 key_name = key_name.lower()
33 try:
34 win32api.keybd_event(KeyBoard.vk_code[key_name], 0, 0, 0)
35 except Exception as e:
36 print('未按下enter键')
37 print(e)
38
39 @staticmethod
40 def keyUp(key_name):
41 """抬起键"""
42 key_name = key_name.lower()
43 win32api.keybd_event(KeyBoard.vk_code[key_name], 0, win32con.KEYEVENTF_KEYUP, 0)
44
45 @staticmethod
46 def oneKey(key):
47 """模拟单个按键"""
48 key = key.lower()
49 KeyBoard.keyDown(key)
50 time.sleep(2)
51 KeyBoard.keyUp(key)
52
53 @staticmethod
54 def twoKeys(key1, key2):
55 """模拟组合按键"""
56 key1 = key1.lower()
57 key2 = key2.lower()
58 KeyBoard.keyDown(key1)
59 KeyBoard.keyDown(key2)
60 KeyBoard.keyUp(key1)
61 KeyBoard.keyUp(key2)
62
63
64 if __name__ == '__main__':
65 from selenium import webdriver
66 driver = webdriver.Firefox()
67 driver.get('http://www.baidu.com')
68 driver.find_element_by_id('kw').send_keys('python')
69 KeyBoard.twoKeys('ctrl', 'a')
70 KeyBoard.twoKeys('ctrl', 'x')
通过测试项目设计,我们需要把测试数据存放在Excel文件中,把页面操作元素存在UI对象库中也就是一个配置文件,那么我们需要对Excel 和 ini文件解析,因此我们开始编写这两个方法,设计UI对象库和测试数据文件
parseExcelFile.py-解析Excel文件
1 """2 ------------------------------------3 @Time : 2019/4/22 16:124 @Auth : linux超5 @File : parseExcelFile.py6 @IDE : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """10 from openpyxl import load_workbook11 from config.conf import excelPath12 13 14 class ParseExcel(object):15 16 def __init__(self):17 self.wk = load_workbook(excelPath)18 self.excelFile = excelPath19 20 def getSheetByName(self, sheetName):21 """获取sheet对象"""22 sheet = self.wk[sheetName]23 return sheet24 25 def getRowNum(self, sheet):26 """获取有效数据的最大行号"""27 return sheet.max_row28 29 def getColsNum(self, sheet):30 """获取有效数据的最大列号"""31 return sheet.max_column32 33 def getRowValues(self, sheet, rowNum):34 """获取某一行的数据"""35 maxColsNum = self.getColsNum(sheet)36 rowValues = []37 for colsNum in range(1, maxColsNum + 1):38 value = sheet.cell(rowNum, colsNum).value39 if value is None:40 value = ''41 rowValues.append(value)42 return tuple(rowValues)43 44 def getColumnValues(self, sheet, columnNum):45 """获取某一列的数据"""46 maxRowNum = self.getRowNum(sheet)47 columnValues = []48 for rowNum in range(2, maxRowNum + 1):49 value = sheet.cell(rowNum, columnNum).value50 if value is None:51 value = ''52 columnValues.append(value)53 return tuple(columnValues)54 55 def getValueOfCell(self, sheet, rowNum, columnNum):56 """获取某一个单元格的数据"""57 value = sheet.cell(rowNum, columnNum).value58 if value is None:59 value = ''60 return value61 62 def getAllValuesOfSheet(self, sheet):63 """获取某一个sheet页的所有测试数据,返回一个元祖组成的列表"""64 maxRowNum = self.getRowNum(sheet)65 columnNum = self.getColsNum(sheet)66 allValues = []67 for row in range(2, maxRowNum + 1):68 rowValues = []69 for column in range(1, columnNum + 1):70 value = sheet.cell(row, column).value71 if value is None:72 value = ''73 rowValues.append(value)74 allValues.append(tuple(rowValues))75 return allValues76 77 78 if __name__ == '__main__':79 # excel = ParseExcel()80 # sheet = excel.getSheetByName('login')81 # print('行号:', excel.getRowNum(sheet))82 # print('列号:', excel.getColsNum(sheet))83 #84 # rowvalues = excel.getRowValues(sheet, 1)85 # columnvalues = excel.getColumnValues(sheet, 2)86 # valueofcell = excel.getValueOfCell(sheet, 1, 2)87 # allvalues = excel.getAllValuesOfSheet(sheet)88 #89 # print('第{}行数据{}'.format(1, rowvalues))90 # print('第{}列数据{}'.format(2, columnvalues))91 # print('{}{}单元格的内容{}'.format(1, 2, valueofcell))92 # print('login{}'.format(allvalues))93 94 excel = ParseExcel()95 sheet = excel.getSheetByName('mail')96 print('行号:', excel.getRowNum(sheet))97 print('列号:', excel.getColsNum(sheet))98 99 allvalues = excel.getAllValuesOfSheet(sheet)
100
101 print('sendmail{}'.format(allvalues))
parseConFile.py-解析配置文件
1 """2 ------------------------------------3 @Time : 2019/4/18 10:544 @Auth : linux超5 @File : parseConFile.py6 @IDE : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 import configparser
11 from config.conf import configDir
12
13
14 class ParseConFile(object):
15
16 def __init__(self):
17 self.file = configDir
18 self.conf = configparser.ConfigParser()
19 self.conf.read(self.file, encoding='utf-8')
20
21 def getAllSections(self):
22 """获取所有的section,返回一个列表"""
23 return self.conf.sections()
24
25 def getAllOptions(self, section):
26 """获取指定section下所有的option, 返回列表"""
27 return self.conf.options(section)
28
29 def getLocatorsOrAccount(self, section, option):
30 """获取指定section, 指定option对应的数据, 返回元祖和字符串"""
31 try:
32 locator = self.conf.get(section, option)
33 if ('->' in locator):
34 locator = tuple(locator.split('->'))
35 return locator
36 except configparser.NoOptionError as e:
37 print('error:', e)
38 return 'error: No option "{}" in section: "{}"'.format(option, section)
39
40 def getOptionValue(self, section):
41 """获取指定section下所有的option和对应的数据,返回字典"""
42 value = dict(self.conf.items(section))
43 return value
44
45
46 if __name__ == '__main__':
47 cf = ParseConFile()
48 print(cf.getAllSections())
49 print(cf.getAllOptions('126LoginAccount'))
50 print(cf.getLocatorsOrAccount('126LoginAccount', 'username'))
51 print(cf.getOptionValue('126LoginAccount'))
config.ini
1 [126LoginAccount];126邮箱正确的登录账号和密码;运行用例时请更换正确的用户名和密码2 username=linuxxiaochao3 password=xiaochao115204 [HomePageElements];126邮箱首页菜单栏元素5 homePage=id->_mail_tabitem_0_3text6 mailList=id->_mail_tabitem_1_4text7 applicationCenter=id->_mail_tabitem_2_5text8 inBox=id->_mail_tabitem_3_6text9 [LoginPageElements];126邮箱登录页面的元素
10 frame=xpath->//div[@id="loginDiv"]/iframe
11 username=xpath->//input[@name="email"]
12 password=xpath->//input[@name="password"]
13 loginBtn=xpath->//a[@id="dologin"]
14 ferrorHead=xpath->//div[@class="ferrorhead"]
15 [ContactPageElements];126邮箱添加联系人页面元素
16 new_contact=xpath->//span[text()="新建联系人"]
17 name=id->input_N
18 mail=xpath->//div[@id="iaddress_MAIL_wrap"]//input[@class="nui-ipt-input"]
19 star=xpath->//span[@class="nui-chk-text"]/preceding-sibling::span/b
20 phone=xpath->//div[@id='iaddress_TEL_wrap']//input[@class='nui-ipt-input']
21 comment=id->input_DETAIL
22 commit=xpath->//span[text()='确 定']
23 tooltip=xpath->//span[text()='请正确填写邮件地址。']
24 [SendMailPageElements];126邮箱发送邮件页面元素
25 writeMail=xpath->//div[@id='dvNavContainer']//span[text()='写 信']
26 addressee=xpath->//input[@aria-label='收件人地址输入框,请输入邮件地址,多人时地址请以分号隔开']
27 subject=xpath->//input[contains(@id, '_subjectInput')]
28 iframe=xpath->//iframe[@class="APP-editor-iframe"]
29 text=xpath->/html/body
30 sendBtn=xpath->//header//span[text()='发送']
31 expect=xpath->//h1[contains(@id,'_succInfo')]
32 uploadAttachment=xpath->//div[@title="点击添加附件"]
33 delete=xpath->//a[text()='删除']
新建excel文件,分3个sheet,分别为:login,contact,mail #每个sheet中数据可自行填写,驱动测试用例执行不同的数据进行测试
login
contact
数据,UI对象库,解析方法都已经有了,接下来通过PageObject模式设计编写每个页面的操作及封装126邮箱的功能,以便后续设计用例调用
BasePage.py-webdriver二次封装
1 """2 ------------------------------------3 @Time : 2019/4/20 8:454 @Auth : linux超5 @File : BasePage.py6 @IDE : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """10 import time11 from selenium.webdriver.support import expected_conditions as EC12 from selenium.webdriver.support.wait import WebDriverWait as wd13 from selenium.webdriver.common.by import By14 from selenium.common.exceptions import NoSuchWindowException, TimeoutException, \15 NoAlertPresentException, NoSuchFrameException16 from selenium import webdriver17 18 from util.clipboard import ClipBoard19 from util.keyboard import KeyBoard20 from util.parseConFile import ParseConFile21 from util.parseExcelFile import ParseExcel22 23 24 class BasePage(object):25 """26 结合显示等待封装一些selenium 内置方法27 """28 cf = ParseConFile()29 excel = ParseExcel()30 31 def __init__(self, driver, outTime=30):32 self.byDic = {33 'id': By.ID,34 'name': By.NAME,35 'class_name': By.CLASS_NAME,36 'xpath': By.XPATH,37 'link_text': By.LINK_TEXT38 }39 self.driver = driver40 self.outTime = outTime41 42 def findElement(self, by, locator):43 """44 find alone element45 :param by: eg: id, name, xpath, css.....46 :param locator: id, name, xpath for str47 :return: element object48 """49 try:50 print('[Info:Starting find the element "{}" by "{}"!]'.format(locator, by))51 element = wd(self.driver, self.outTime).until(lambda x : x.find_element(by, locator))52 except TimeoutException as t:53 print('error: found "{}" timeout!'.format(locator), t)54 except NoSuchWindowException as e:55 print('error: no such "{}"'.format(locator), e)56 except Exception as e:57 raise e58 else:59 # print('[Info:Had found the element "{}" by "{}"!]'.format(locator, by))60 return element61 62 def findElements(self, by, locator):63 """64 find group elements65 :param by: eg: id, name, xpath, css.....66 :param locator: eg: id, name, xpath for str67 :return: elements object68 """69 try:70 print('[Info:start find the elements "{}" by "{}"!]'.format(locator, by))71 elements = wd(self.driver, self.outTime).until(lambda x : x.find_element(by, locator))72 except TimeoutException as t:73 print(t)74 except NoSuchWindowException as e:75 print(e)76 except Exception as e:77 raise e78 else:79 # print('[Info:Had found the elements "{}" by "{}"!]'.format(locator, by))80 return elements81 82 def isElementExsit(self, by, locator):83 """84 assert element if exist85 :param by: eg: id, name, xpath, css.....86 :param locator: eg: id, name, xpath for str87 :return: if element return True else return false88 """89 if by.lower() in self.byDic:90 try:91 wd(self.driver, self.outTime).\92 until(EC.visibility_of_element_located((self.byDic[by], locator)))93 except TimeoutException:94 print('Error: element "{}" time out!'.format(locator))95 return False96 except NoSuchWindowException:97 print('Error: element "{}" not exsit!'.format(locator))98 return False99 return True
100 else:
101 print('the "{}" error!'.format(by))
102
103 def isClick(self, by, locator):
104 """判断是否可点击,返回元素对象"""
105 if by.lower() in self.byDic:
106 try:
107 element = wd(self.driver, self.outTime).\
108 until(EC.element_to_be_clickable((self.byDic[by], locator)))
109 except Exception:
110 return False
111 return element
112 else:
113 print('the "{}" error!'.format(by))
114
115 def isAlertAndSwitchToIt(self):
116 """
117 assert alert if exsit
118 :return: alert obj
119 """
120 try:
121 re = wd(self.driver, self.outTime).until(EC.alert_is_present())
122 except NoAlertPresentException:
123 return False
124 except Exception:
125 return False
126 return re
127
128 def switchToFrame(self, by, locator):
129 """判断frame是否存在,存在就跳到frame"""
130 print('info:switching to iframe "{}"'.format(locator))
131 if by.lower() in self.byDic:
132 try:
133 wd(self.driver, self.outTime).\
134 until(EC.frame_to_be_available_and_switch_to_it((self.byDic[by], locator)))
135 except TimeoutException as t:
136 print('error: found "{}" timeout!'.format(locator), t)
137 except NoSuchFrameException as e:
138 print('error: no such "{}"'.format(locator), e)
139 except Exception as e:
140 raise e
141 else:
142 print('the "{}" error!'.format(by))
143
144 def switchToDefaultFrame(self):
145 """返回默认的frame"""
146 print('info:switch back to default iframe')
147 try:
148 self.driver.switch_to.default_content()
149 except Exception as e:
150 print(e)
151
152 def getAlertText(self):
153 """获取alert的提示信息"""
154 if self.isAlertAndSwitchToIt():
155 alert = self.isAlertAndSwitchToIt()
156 return alert.text
157 else:
158 return None
159
160 def getElementText(self, by, locator, name=None):
161 """获取某一个元素的text信息"""
162 try:
163 element = self.findElement(by, locator)
164 if name:
165 return element.get_attribute(name)
166 else:
167 return element.text
168 except:
169 print('get "{}" text failed return None'.format(locator))
170 return None
171
172 def loadUrl(self, url):
173 """加载url"""
174 print('info: string upload url "{}"'.format(url))
175 self.driver.get(url)
176
177 def getSource(self):
178 """获取页面源码"""
179 return self.driver.page_source
180
181 def sendKeys(self, by, locator, value=''):
182 """写数据"""
183 print('info:input "{}"'.format(value))
184 try:
185 element = self.findElement(by, locator)
186 element.send_keys(value)
187 except AttributeError as e:
188 print(e)
189
190 def clear(self, by, locator):
191 """清理数据"""
192 print('info:clearing value')
193 try:
194 element = self.findElement(by, locator)
195 element.clear()
196 except AttributeError as e:
197 print(e)
198
199 def click(self, by, locator):
200 """点击某个元素"""
201 print('info:click "{}"'.format(locator))
202 element = self.isClick(by, locator)
203 if element:
204 element.click()
205 else:
206 print('the "{}" unclickable!')
207
208 def sleep(self, num=0):
209 """强制等待"""
210 print('info:sleep "{}" minutes'.format(num))
211 time.sleep(num)
212
213 def ctrlV(self, value):
214 """ctrl + V 粘贴"""
215 print('info:pasting "{}"'.format(value))
216 ClipBoard.setText(value)
217 self.sleep(3)
218 KeyBoard.twoKeys('ctrl', 'v')
219
220 def enterKey(self):
221 """enter 回车键"""
222 print('info:keydown enter')
223 KeyBoard.oneKey('enter')
224
225 def waitElementtobelocated(self, by, locator):
226 """显示等待某个元素出现,且可见"""
227 print('info:waiting "{}" to be located'.format(locator))
228 try:
229 wd(self.driver, self.outTime).until(EC.visibility_of_element_located((self.byDic[by], locator)))
230 except TimeoutException as t:
231 print('error: found "{}" timeout!'.format(locator), t)
232 except NoSuchWindowException as e:
233 print('error: no such "{}"'.format(locator), e)
234 except Exception as e:
235 raise e
236
237 def assertValueInSource(self, value):
238 """断言某个关键字是否存在页面源码中"""
239 print('info:assert "{}" in page source'.format(value))
240 source = self.getSource()
241 assert value in source, '关键字"{}"不存在源码中!'.format(value)
242
243 def assertStringContainsValue(self, String, value):
244 """断言某段字符串包含另一个字符串"""
245 print('info:assert "{}" contains "{}"'.format(String, value))
246 assert value in String, '"{}"不包含"{}"!'.format(String, value)
247
248
249 @staticmethod
250 def getSheet(sheetName):
251 """获取某个sheet页的对象"""
252 sheet = BasePage.excel.getSheetByName(sheetName)
253 return sheet
254
255
256 if __name__ == "__main__":
257 driver = webdriver.Firefox()
258 frame = ('xpath', '//div[@id="loginDiv"]/ifram')
259 wait = BasePage(driver)
260 driver.get('https://mail.126.com/')
261 wait.switchToFrame(*frame)
262 username = wait.findElement('xpath', '//input[@name="email"]')
263 username.send_keys('账号')
264 if wait.isElementExsit('xpath', '//input[@name="password"]'):
265 wait.findElement('xpath', '//input[@name="password"]').send_keys('xiaochao11520')
266 wait.click('xpath', '//a[@id="dologin"]')
HomePage.py-邮箱首页选择菜单
1 """2 ------------------------------------3 @Time : 2019/4/20 12:284 @Auth : linux超5 @File : HomePage.py6 @IDE : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 from Page.BasePage import BasePage
11
12
13 class HomePage(BasePage):
14 # 配置文件读取元素
15 homePage = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'homePage')
16 mailList = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'mailList')
17 applicationCenter = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'applicationCenter')
18 inBox = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'inBox')
19 '''首页菜单选项'''
20 def selectMenu(self, Menu='mailList'):
21 """邮箱首页选择菜单"""
22 if Menu == 'mailList':
23 self.click(*HomePage.mailList)
24 elif Menu == 'homePage':
25 self.click(*HomePage.homePage)
26 elif Menu == 'applicationCenter':
27 self.click(*HomePage.applicationCenter)
28 elif Menu == 'inBox':
29 self.click(*HomePage.inBox)
30 else:
31 raise ValueError('''
32 菜单选择错误!
33 homePage->首页
34 mailList->通讯录
35 applicationCenter->应用中心
36 inBox->收件箱''')
37
38 if __name__=='__main__':
39 from selenium import webdriver
40 from Page.PageObject.LoginPage import LoginPage
41 driver = webdriver.Firefox()
42 login = LoginPage(driver)
43 login.login('账号', 'xiaochao11520')
44
45 home = HomePage(driver)
46 home.selectMenu()
LoginPage.py-封装登录功能
1 """2 ------------------------------------3 @Time : 2019/4/20 12:284 @Auth : linux超5 @File : LoginPage.py6 @IDE : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 from Page.BasePage import BasePage
11
12
13 class LoginPage(BasePage):
14
15 # 配置文件读取元素
16 frame = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'frame')
17 username = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'username')
18 password = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'password')
19 loginBtn = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'loginBtn')
20 ferrorHead = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'ferrorHead') # 登录失败提示
21
22 def login(self, userName, passWord):
23 '''登录'''
24 print('-------staring login-------')
25 self.loadUrl('https://mail.126.com')
26 self.switchToFrame(*LoginPage.frame)
27 self.clear(*LoginPage.username)
28 self.sendKeys(*LoginPage.username, userName)
29 self.clear(*LoginPage.password)
30 self.sendKeys(*LoginPage.password, passWord)
31 self.click(*LoginPage.loginBtn)
32 self.switchToDefaultFrame()
33 print('---------end login---------')
34
35 # add at 2019/04/19
36 def assertTextEqString(self, expected, name = None):
37 '''断言提示信息是否与期望的值相等'''
38 self.switchToFrame(*LoginPage.frame)
39 text = self.getElementText(*LoginPage.ferrorHead, name)
40 self.switchToDefaultFrame()
41 print('info: assert "{}" == "{}"'.format(text, expected))
42 assert text == expected, '{} != {}'.format(text, expected)
43
44 if __name__=="__main__":
45 from selenium import webdriver
46 driver = webdriver.Firefox()
47 login = LoginPage(driver, 30)
48 login.login('lin', '')
49 login.assertTextEqString('请输入密码')
ContactPage.py-封装添加联系人功能
1 """2 ------------------------------------3 @Time : 2019/4/20 12:294 @Auth : linux超5 @File : ContactPage.py6 @IDE : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 from Page.BasePage import BasePage
11
12
13 class ContactPage(BasePage):
14 # 配置文件读取元素
15 new_contact = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'new_contact')
16 name = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'name')
17 mail = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'mail')
18 star = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'star')
19 phone = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'phone')
20 comment = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'comment')
21 commit = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'commit')
22 errortip = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'tooltip') # 错误提示
23
24 def newContact(self, Name, Mail, Star, Phone, Comment):
25 """添加联系人"""
26 print('--------string add contact--------')
27 self.click(*ContactPage.new_contact)
28 self.sendKeys(*ContactPage.name, Name)
29 self.sendKeys(*ContactPage.mail, Mail)
30 if Star == '1':
31 self.click(*ContactPage.star)
32 self.sendKeys(*ContactPage.phone, Phone)
33 self.sendKeys(*ContactPage.comment, Comment)
34 self.click(*ContactPage.commit)
35 print('--------end add contact--------')
36
37 def assertErrorTip(self, excepted):
38 """断言联系人添加失败时是否有提示信息"""
39 text = self.getElementText(*ContactPage.errortip)
40 print('info: assert "{}"=="{}"'.format(text, excepted))
41 assert text == excepted
42
43 if __name__ == '__main__':
44 from selenium import webdriver
45 from Page.PageObject.LoginPage import LoginPage
46 from Page.PageObject.HomePage import HomePage
47 driver = webdriver.Firefox()
48 home = HomePage(driver)
49 login = LoginPage(driver)
50 contact = ContactPage(driver)
51
52 login.login('账号', 'xiaochao11520')
53 home.selectMenu()
54 contact.newContact('281754041@qq.com')
SendMailPage.py-封装发送邮件功能
1 """2 ------------------------------------3 @Time : 2019/4/20 9:164 @Auth : linux超5 @File : SendMailPage.py6 @IDE : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 from Page.BasePage import BasePage
11
12
13 class SendMailPage(BasePage):
14 # 配置文件读取元素
15 writeMail = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'writeMail')
16 addressee = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'addressee')
17 subject = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'subject')
18 iframe = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'iframe')
19 text = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'text')
20 sendBtn = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'sendBtn')
21 expect = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'expect')
22 uploadAttachment = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'uploadAttachment')
23 delete = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'delete')
24
25 def sendMail(self, Address, Subject, Text, PFA=''):
26 """发送邮件功能"""
27 print('------------string send mail---------------------')
28 self.click(*SendMailPage.writeMail)
29 self.sendKeys(*SendMailPage.addressee, Address)
30 self.sendKeys(*SendMailPage.subject, Subject)
31 self.switchToFrame(*SendMailPage.iframe)
32 self.sendKeys(*SendMailPage.text, Text)
33 self.switchToDefaultFrame()
34 if PFA:
35 self.click(*SendMailPage.uploadAttachment)
36 self.ctrlV(PFA)
37 self.enterKey()
38 self.waitElementtobelocated(*SendMailPage.delete)
39 self.click(*SendMailPage.sendBtn)
40 print('------------end send mail---------------------')
41
42 if __name__=='__main__':
43 from Page.PageObject.LoginPage import LoginPage
44 from selenium import webdriver
45 driver = webdriver.Firefox()
46
47 login = LoginPage(driver)
48 login.login('账号', 'xiaochao11520')
49 sendMail = SendMailPage(driver)
50 sendMail.sendMail('281754043@qq.com', 'pytest', 'pytest实战实例', 1, 'D:\KeyWordDriverTestFrameWork\geckodriver.log')
所有的准备工作都已经做好了,还有一个问题,我们的添加联系人和发送邮件应该是否应该在已经登录的前提下测试呢?答案是肯定的。所以我们在用例同目录下新建conftest.py文件并调用登录功能。
conftest.py-同用例目录下,调用登录功能
1 """2 ------------------------------------3 @Time : 2019/4/20 15:104 @Auth : linux超5 @File : conftest.py6 @IDE : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 import pytest
11 from Page.PageObject.LoginPage import LoginPage
12
13
14 # 从配置文件中获取正确的用户名和密码
15 userName = LoginPage.cf.getLocatorsOrAccount('126LoginAccount', 'username')
16 passWord = LoginPage.cf.getLocatorsOrAccount('126LoginAccount', 'password')
17 @pytest.fixture(scope='function')
18 def login(driver):
19 '''除登录用例,每一个用例的前置条件'''
20 print('------------staring login------------')
21 loginFunc = LoginPage(driver, 30)
22 loginFunc.login(userName, passWord)
23 yield
24 print('------------end login------------')
25 driver.delete_all_cookies()
ok,开始编写测试用例啦
test_loginCase.py-登录功能测试
1 """2 ------------------------------------3 @Time : 2019/4/20 14:104 @Auth : linux超5 @File : test_loginCase.py6 @IDE : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 import pytest
11 from Page.PageObject.LoginPage import LoginPage
12
13
14 @pytest.mark.loginTest
15 class TestLogin(object):
16
17 # 测试数据
18 loginSheet = LoginPage.getSheet('login')
19 data = LoginPage.excel.getAllValuesOfSheet(loginSheet)
20
21 # 正确的帐号和密码
22 userName = LoginPage.cf.getLocatorsOrAccount('126LoginAccount', 'username')
23 passWord = LoginPage.cf.getLocatorsOrAccount('126LoginAccount', 'password')
24
25 @pytest.fixture()
26 def teardown_func(self, driver):
27 """
28 执行每个用例之后要清除一下cookie,
29 否则你第一个账号登录之后,重新加载网址还是登录状态,无法测试后面的账号
30 """
31 yield
32 driver.delete_all_cookies()
33
34 @pytest.mark.parametrize('username, password, expect', data)
35 def test_login(self, teardown_func, driver, username, password, expect):
36 """测试登录"""
37 login = LoginPage(driver, 30)
38 login.login(username, password)
39 login.sleep(5)
40 # 增加登录失败时, 对提示信息的验证
41 if username == TestLogin.userName and password == TestLogin.passWord:
42 login.assertValueInSource(expect)
43 elif username == '':
44 login.assertTextEqString(expect)
45 elif username != '' and password == '':
46 login.assertTextEqString(expect)
47 elif username == '' and password == '':
48 login.assertTextEqString(expect)
49 else:
50 login.assertTextEqString(expect)
51
52
53 if __name__ == "__main__":
54 pytest.main(['-v', 'test_loginCase.py'])
test_contactCase.py-添加联系人功能测试
1 """2 ------------------------------------3 @Time : 2019/4/20 16:154 @Auth : linux超5 @File : test_contactCase.py6 @IDE : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 import re
11 import pytest
12 from Page.PageObject.HomePage import HomePage
13 from Page.PageObject.ContactPage import ContactPage
14
15
16 @pytest.mark.conatctTest
17 class TestAddContact(object):
18
19 # 测试数据
20 contactSheet = ContactPage.getSheet('contact')
21 data = ContactPage.excel.getAllValuesOfSheet(contactSheet)
22
23 @pytest.mark.newcontact
24 @pytest.mark.parametrize('Name, Mail, Star, Phone, Comment, expect', data)
25 def test_NewContact(self, driver, login, Name, Mail, Star, Phone, Comment, expect):
26 """测试添加联系人"""
27 home_page = HomePage(driver)
28 contact_page = ContactPage(driver)
29 home_page.selectMenu()
30 contact_page.newContact(Name, Mail, Star, Phone, Comment)
31 home_page.sleep(5)
32 # 校验错误的邮箱是否提示信息正确
33 if re.match(r'^.{1,}@[0-9a-zA-Z]{1,13}\..*$', Mail):
34 contact_page.assertValueInSource(expect)
35 else:
36 contact_page.assertErrorTip(expect)
37
38 if __name__ == '__main__':
39 pytest.main(['-v', 'test_contactCase.py'])
test_sendMailCase.py-发送邮件功能测试
1 """2 ------------------------------------3 @Time : 2019/4/20 10:044 @Auth : linux超5 @File : test_sendMailCase.py6 @IDE : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 import pytest
11 from Page.PageObject.SendMailPage import SendMailPage
12
13 @pytest.mark.sendMailTest
14 class TestSendMail(object):
15
16 sendMailSheet = SendMailPage.getSheet('mail')
17 data = SendMailPage.excel.getAllValuesOfSheet(sendMailSheet)
18
19 @pytest.mark.sendmail
20 @pytest.mark.parametrize('Address, Subject, Text, PFA', data)
21 def test_sendMail(self, driver, login, Address, Subject, Text,PFA):
22 """测试发送邮件,包括带附件的邮件"""
23 send_mail = SendMailPage(driver)
24 send_mail.sendMail(Address, Subject, Text, PFA)
25 send_mail.sleep(5)
26 assert send_mail.isElementExsit(*SendMailPage.expect)
27
28 if __name__=='__main__':
29 pytest.main(['-v', 'test_sendMailCase.py'])
问题
用例已经写完了,有两个问题
1.有没有发现我们的报告怎么生成的?也没有失败用例截图?
2.我们貌似并没有编写驱动浏览器的代码?
现在我们来解决这个两个问题
根据pytest的conftest.py文件的原理,我们可以把驱动浏览器的代码写在一个全局的conftest.py文件里面。报告生成其实是通过命令 pytest --html=‘report.html’ --self-contained-html生成的,但是这样的报告对用例的描述不是很清晰,且没有对失败用例截图,也不方便我们分析项目的缺陷,我们也可以填写代码放到这个文件里面
conftest.py-全局conftest.py文件
1 """2 ------------------------------------3 @Time : 2019/4/12 14:104 @Auth : linux超5 @File : conftest.py6 @IDE : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 import pytest
11 from selenium import webdriver
12 from py._xmlgen import html
13
14 _driver = None
15 # 测试失败时添加截图和测试用例描述(用例的注释信息)
16
17 @pytest.mark.hookwrapper
18 def pytest_runtest_makereport(item):
19 """
20 当测试失败的时候,自动截图,展示到html报告中
21 :param item:
22 """
23 pytest_html = item.config.pluginmanager.getplugin('html')
24 outcome = yield
25 report = outcome.get_result()
26 extra = getattr(report, 'extra', [])
27
28 if report.when == 'call' or report.when == "setup":
29 xfail = hasattr(report, 'wasxfail')
30 if (report.skipped and xfail) or (report.failed and not xfail):
31 file_name = report.nodeid.replace("::", "_")+".png"
32 screen_img = _capture_screenshot()
33 if file_name:
34 html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:600px;height:300px;" ' \
35 'onclick="window.open(this.src)" align="right"/></div>' % screen_img
36 extra.append(pytest_html.extras.html(html))
37 report.extra = extra
38 report.description = str(item.function.__doc__)
39 report.nodeid = report.nodeid.encode("utf-8").decode("unicode_escape")
40
41
42 @pytest.mark.optionalhook
43 def pytest_html_results_table_header(cells):
44 cells.insert(1, html.th('Description'))
45 cells.insert(2, html.th('Test_nodeid'))
46 cells.pop(2)
47
48
49 @pytest.mark.optionalhook
50 def pytest_html_results_table_row(report, cells):
51 cells.insert(1, html.td(report.description))
52 cells.insert(2, html.td(report.nodeid))
53 cells.pop(2)
54
55
56 def _capture_screenshot():
57 """
58 截图保存为base64
59 :return:
60 """
61 return _driver.get_screenshot_as_base64()
62 # 这里我设置的级别是模块级别,也就是每个测试文件运行一次
63 # 可以设置为session,全部用例执行一次,但是针对126邮箱的话
64 # 登录次数太多会叫你验证,如果验证就没法执行用例了,我没有对验证处理(处理比较复杂)
65
66
67 @pytest.fixture(scope='module')
68 def driver():
69 global _driver
70 print('------------open browser------------')
71 _driver = webdriver.Firefox()
72
73 yield _driver
74 print('------------close browser------------')
75 _driver.quit()
最后呢,为了减小项目维护成本,我们把一些全局的配置项,放到我们的功能配置文件中共全局使用,包括运行用例的一些命令字符串,可以自行修改
conf.py-全局配置文件
1 """2 ------------------------------------3 @Time : 2019/4/20 16:504 @Auth : linux超5 @File : conf.py6 @IDE : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 from datetime import datetime
11 import os
12 # 项目根目录
13 projectDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
14 # 报告目录
15 reportDir = os.path.join(projectDir, 'report')
16 # ui对象库config.ini文件所在目录
17 configDir = os.path.join(projectDir, 'config', 'config.ini')
18 # 测试数据所在目录
19 excelPath = os.path.join(projectDir, 'data', 'tcData.xlsx')
20 # 当前时间
21 currentTime = datetime.now().strftime('%H_%M_%S')
22
23 # 邮件配置信息
24 # 邮件服务器
25 smtpServer = 'smtp.qq.com'
26 # 发送者
27 fromUser = '账号@qq.com'
28 # 发送者密码
29 fromPassWord = 'mhxvqpewblldbjhf'
30 # 接收者
31 toUser = ['账号@qq.com']# 可以同时发送给多人,追加到列表中
32 # 邮件标题
33 subject = 'xx项目自动化测试报告'
34 # 邮件正文
35 contents = '测试报告正文'
36 # 报告名称
37 htmlName = r'{}\testReport{}.html'.format(reportDir, currentTime)
38
39 # 脚本执行命令
40 args = r'pytest --html=' + htmlName+ ' ' + '--self-contained-html'
41 # modify by linuxchao at 2019/4/25
42 args_login = r'pytest --html='+ htmlName+ ' ' + '-m' + ' ' + 'loginTest'+ ' --self-contained-html'
43 args_contact = r'pytest --html='+ htmlName+ ' ' + '-m' + ' ' + 'contactTest'+ ' --self-contained-html'
44 args_sendmail = r'pytest --html='+ htmlName+ ' ' + '-m' + ' ' + 'sendMailTest'+ ' --self-contained-html'
运行项目
通过命令运行
1.cmd切换到项目的根目录,执行pytest --html=‘report.html’ --self-contained-html命令(此运行方式,无法发送测试报告邮件)
这种方式感觉有点low,我们换另外一种方式,可以通过os模块自动执行相关命令,编写运行用例代码
RunTestCase.py-执行用例文件
1 """2 ------------------------------------3 @Time : 2019/4/15 16:144 @Auth : linux超5 @File : RunTestCase.py6 @IDE : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 import sys
11 sys.path.append('.')
12 from config.conf import *
13 from util.sendMailForReprot import SendMailWithReport
14
15
16 def main():
17 # 判断项目的根目录是否在sys.path中,没有就添加
18 if projectDir not in sys.path:
19 sys.path.append(projectDir)
20 # 执行用例
21 os.system(args)
22 # 发送邮件
23 SendMailWithReport.send_mail(
24 smtpServer, fromUser, fromPassWord,
25 toUser, subject, contents,
26 htmlName)
27
28
29 if __name__ == '__main__':
30 main()
我们可以直接执行这个文件执行所用的测试用例了!
其实我们运行用例往往不只是 使用pytest --html=‘report.html’ --self-contained-html 这样一个简单的命令运行,通常会添加很多的命令选项,比如-v,-q,-s等等,那么怎么办呢?这时候就用到了pytest.ini配置文件了
只添加了几个简单的命令选项
pytest.ini-pytest配置文件
1 [pytest]
2 addopts=-vqs
3 testpaths=./TestCases
4 markers=
5 loginTest: Run login test cases
6 contactTest: Run add contact test cases
7 sendMailTest: Run send mail test cases
测试输出
1.自动生成html格式报告,其中报告里面附带用例执行日志明细,及用例失败自动截图(部分报告展示)
2.自动发送测试邮件给指定用户
相关文章:
Pytest+selenium UI自动化测试实战实例
今天来说说pytest吧,经过几周的时间学习,有收获也有疑惑,总之最后还是搞个小项目出来证明自己的努力不没有白费。 环境准备 1 确保您已经安装了python3.x 2 配置python3pycharmselenium2开发环境 3 安装pytest库pip install p…...
黑马点评 - 商铺类型缓存练习题(Redis List实现)
首先明确返回值是一个 List<ShopType> 类型那么我们修改此函数并在 TypeService 中声明 queryTypeList 方法,并在其实现类中实现此方法 GetMapping("list")public Result queryTypeList() {return typeService.queryTypeList();}实现此方法首先需要…...
C++ 创建和配置dll与lib库
C简明教程(13)创建和配置dll与lib库_怎样生成lib库和dll库-CSDN博客 C 动态库与静态库详解 一、为什么要引入库的概念 在 C 编程中,随着项目规模的不断扩大,代码量也会急剧增加。如果将所有代码都写在一个源文件中,…...
深度剖析 Veo2 工具:解锁 AI 视频创作新境界
在当下这个 AI 技术日新月异的时代,各种 AI 工具如雨后春笋般涌现,让人目不暇接。今天,我就来给大家好好说道说道谷歌旗下的 Veo2,这可是一款在 AI 视频创作领域相当有分量的工具。好多朋友都在问,Veo2 到底厉害在哪?好不好上手?能在哪些地方派上用场?别着急,今天我就…...
LabVIEW自定义测量参数怎么设置?
以下通过一个温度采集案例,说明在 LabVIEW 中设置自定义测量参数的具体方法: 案例背景 假设使用 NI USB-6009 数据采集卡 和 热电偶传感器 监测温度,需自定义以下参数: 采样率:1 kHz 输入量程:0~10 V&a…...
JVM执行流程与架构(对应不同版本JDK)
直接上图(对应JDK8以及以后的HotSpot) 这里主要区分说明一下 方法区于 字符串常量池 的位置更迭: 方法区 JDK7 以及之前的版本将方法区存放在堆区域中的 永久代空间,堆的大小由虚拟机参数来控制。 JDK8 以及之后的版本将方法…...
数据治理项目为什么沦为了PPT工程?
数据治理项目为什么沦为了PPT工程? 数据治理项目为什么沦为PPT工程数据治理项目面临的深层挑战数据治理项目的破局之道 "这个项目明明做了快一年了,怎么感觉还在原地踏步?"数据治理小张最近很烦恼。 整天泡在会议室里,写…...
module ‘matplotlib.cm‘ has no attribute ‘get_cmap‘
目录 解决方法1: 解决方法2,新版api改了: module matplotlib.cm has no attribute get_cmap 报错代码: cmap matplotlib.cm.get_cmap(Oranges) 解决方法1: pip install matplotlib3.7.3 解决方法2,新版…...
HTML5 教程之标签(3)
HTML5 <center> 标签 (已废弃) 定义和用法 <center> 标签对其包围的文本进行水平居中处理。HTML5不支持使用<center>标签,因此有关该标签的更多信息,请参考“HTML <center>标签”部分! 示例: <center>这个…...
告别传统办公软件,这款编辑器让你事半功倍!
文章目录 1 界面的多样性2 性能优化3 文档编辑器的新功能4 外部文本支持5 体验感想 ONLYOFFICE最近发布了文档8.2版本,带来了众多新特性和性能改进。作为一名用户和开发者,我对这些更新进行了深入的体验,感受到了不少亮点。 新版本特别强调了…...
AI协助探索AI新构型自动化创新的技术实现
一、AI自进化架构的核心范式 1. 元代码生成与模块化重构 - 代码级自编程:基于神经架构搜索的强化学习框架,AI可通过生成元代码模板(框架的抽象层定义、神经元结点-网络拓扑态的编码抽象定义)自动组合功能模块。例如࿰…...
全能型免费内网穿透工具,全面支持macOS、Windows、Linux及Docker系统
1. 登陆官网网址并注册帐号 ngrok | API Gateway, Kubernetes Networking Secure Tunnels 2 下载并安装工具 3. 启动工具 在命令行执行 ngrok http http://localhost:8080 其中端口可换成用户自己想要穿透的端口 4. 获取穿透地址 命令执行后会出现如下画面,红…...
Web - CSS3浮动定位与背景样式
概述 这篇文章主要介绍了 CSS3 中的浮动定位、背景样式、变形效果等内容。包括 BFC 规范与创建方法、浮动的功能与使用要点、定位的多种方式及特点、边框与圆角的设置、背景的颜色、图片等属性、多种变形效果及 3D 旋转等,还提到了浏览器私有前缀。 BFC规范与浏览…...
VUE之组件通信(二)
1、v-model v-model的底层原理:是:value值和input事件的结合 $event到底是啥?啥时候能.target 对于原生事件,$event就是事件对象 ,能.target对应自定义事件,$event就是触发事件时,所传递的数据ÿ…...
Gauss高斯:建表语法,存储方式,OLTP和OLAP,系统时间,数组,分组(grouping set,rollup)
数据库和表的语法 数据库 表 oracle,高斯, hive的默认存储方式都是列式存储 存储方式 高斯数据库(GaussDB)支持列式存储和行式存储 OLTP 与 OLAP OLTP(联机事务处理,Online Transaction Processing)是一种用于管理…...
Java基础进阶
Java基础进阶 异常 概述 异常就是程序出现了不正常的情况 具体分为:Throwable—>(Error Exception);Exception—>(RuntimeException 非RuntimeException) Throwable类是Java语言中所有错误和异常的祖宗类;(上面还有Object类) Thr…...
【数据结构】链表应用1
链表应用 面试题 02.02.返回倒数第k个节点题目描述思路解题过程复杂度 查找相同后缀题目描述解题思路完整代码: 删除绝对值相等的节点题目描述解题思路代码 面试题 02.02.返回倒数第k个节点 题目描述 实现一种算法,找出单向链表中倒数第 k 个节点。返回…...
python gltf生成预览图
使用Python生成GLTF模型的预览图 随着3D技术的不断发展,GLTF(GL Transmission Format)逐渐成为了Web和移动应用程序中最流行的3D文件格式之一。GLTF文件不仅能以较小的体积存储复杂的3D模型,还支持动画、材质、光照和纹理等特性。…...
HTTP和HTTPS协议详解
HTTP和HTTPS协议详解 HTTP详解什么是http协议http协议的发展史http0.9http1.0http1.1http2.0 http协议的格式URI和URL请求request响应response http协议完整的请求与响应流程 HTTPS详解为什么使用HTTPSSSL协议HTTPS通信过程TLS协议 HTTP详解 什么是http协议 1、全称Hyper Tex…...
实战:利用百度站长平台加速网站收录
本文转自:百万收录网 原文链接:https://www.baiwanshoulu.com/33.html 利用百度站长平台加速网站收录是一个实战性很强的过程,以下是一些具体的步骤和策略: 一、了解百度站长平台 百度站长平台是百度为网站管理员提供的一系列工…...
专门记录台式电脑常见问题
1、蓝屏死机,检查内存硬盘和cpu 2、拆内存条,用橡皮擦金手指 3、放主板静电,扣主板电池 4、系统时间不正确,主板电池没电 5、开机键坏了 6、电脑主机的风扇转,正常通电运行,但显示器没信号。看键盘的num键&…...
数据库系统概念第六版记录 一
1.关系型数据库 关系型数据库(Relational Database,简称 RDB)是基于关系模型的一种数据库,它通过表格的形式来组织和存储数据。每个表由若干行(记录)和列(字段)组成,数据…...
本地Ollama部署DeepSeek R1模型接入Word
目录 1.本地部署DeepSeek-R1模型 2.接入Word 3.效果演示 4.问题反馈 上一篇文章办公新利器:DeepSeekWord,让你的工作更高效-CSDN博客https://blog.csdn.net/qq_63708623/article/details/145418457?spm1001.2014.3001.5501https://blog.csdn.net/qq…...
Meta Sapiens AI论文解读:人类视觉模型基石初现,AI 未来走向何方?
一、引言 在本文中,我们将深入探讨 Meta AI 的一项新成果,该成果发表于一篇题为《Sapiens:人类视觉模型的基础》的研究论文中。这篇论文介绍了一系列模型,这些模型针对四项以人类为中心的基本任务,正如我们在上面的演示…...
输入类控件和多元素控件【QT】
文章目录 输入类控件QLineEdit Text EditCombo BoxSpin BoxDialSlider多元素控件QListWidget TableWidetTreeWidgetQGroupBoxTab Widget# QVBoxLayout# QHBoxLayoutQGridLayoutQFormLayout 输入类控件 QLineEdit 例如: 实现一个用户输入姓名 密码 电话 性别 的功能…...
一键开启/关闭deepseek
一键开启/关闭 Deepseek对应下载的模型一键开启 Deepseek,一键关闭Deepseek双击对应的bat,就可以启动https://mbd.pub/o/bread/Z56YmpZvbat 下载:https://mbd.pub/o/bread/Z56YmpZv 可以自己写下来,保存成bat文件,也可…...
gitea - fatal: Authentication failed
文章目录 gitea - fatal: Authentication failed概述run_gitea_on_my_pkm.bat 笔记删除windows凭证管理器中对应的url认证凭证启动gitea服务端的命令行正常用 TortoiseGit 提交代码备注END gitea - fatal: Authentication failed 概述 本地的git归档服务端使用gitea. 原来的用…...
Spring AI 智能体通过 MCP 集成本地文件数据
作者:刘军 Model Context Protocol(MCP)简介 模型上下文协议(即 Model Context Protocol,MCP) [ 1] 是一个开放协议,它规范了应用程序如何向大型语言模型(LLM)提供上下…...
音视频入门基础:RTP专题(5)——FFmpeg源码中,解析SDP的实现
一、引言 FFmpeg源码中通过ff_sdp_parse函数解析SDP。该函数定义在libavformat/rtsp.c中: int ff_sdp_parse(AVFormatContext *s, const char *content) {const char *p;int letter, i;char buf[SDP_MAX_SIZE], *q;SDPParseState sdp_parse_state { { 0 } }, *s1…...
MyBatis XML文件配置
目录 一、 配置连接字符串和MyBatis 二、书写持久层代码 2.1 添加Mapper接口 2.2 添加UserlnfoXMLMapper.xml 三、增删改查 3.1 、增(Insert) 3.2、删(Delete) 3.3、改 (Update) 3.4、查 (Select) MyBatisXML的方式需要以下两步&am…...
【Leetcode 热题 100】1143. 最长公共子序列
问题背景 给定两个字符串 t e x t 1 text_1 text1 和 t e x t 2 text_2 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 0 0。 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变…...
【算法】动态规划专题④ ——LCS(最长公共子序列)+ LPS(最长回文子序列) python
目录 前置知识LCS举一反三LPS 前置知识 【算法】动态规划专题③ ——二维DP python 子序列定义为: 不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。 LCS 最长公共子序列 https://www.lanqiao.cn/problems/1189/learning/?p…...
Cesium点集中获取点的id,使用viewer.value.entities.getById报错的解决方法
错误代码: viewer.value.entities.getById(pickedObject.id) 报错: 可以正常获取movement.position但是一直出现如下报错,无法获得航点的id,通过断点定位为 viewer.value.entities.getById(pickedObject.id)导致的报错 解决方…...
360手机刷机 360手机解Bootloader 360手机ROOT
360手机刷机 360手机解Bootloader 360手机ROOT 问:360手机已停产,现在和以后,能刷机吗? 答:360手机,是肯定能刷机的 360手机资源下载网站 360手机-360手机刷机RootTwrp 360os.top 360rom.github.io 一、…...
深度探索DeepSeek-R1:AI大模型的本地应用与个人知识库构建
深度探索DeepSeek-R1:AI大模型的本地应用与个人知识库构建 引言 在当今这个信息爆炸的时代,如何高效地存储、处理和获取知识,已经成为每个人面临的挑战。想象一下,如果你能在没有互联网连接的情况下,构建一个属于自己…...
LabVIEW图像采集与应变场测量系统
开发了一种基于LabVIEW的图像采集与应变场测量系统,提供一种高精度、非接触式的测量技术,用于监测物体的全场位移和应变。系统整合了实时监控、数据记录和自动对焦等功能,适用于工程应用和科学研究。 项目背景 传统的位移和应变测量技术往往…...
解决DeepSeek服务器繁忙问题:本地部署与优化方案
deepseek服务器崩了,手把手教你如何在手机端部署一个VIP通道! 引言 随着人工智能技术的快速发展,DeepSeek等大语言模型的应用越来越广泛。然而,许多用户在使用过程中遇到了服务器繁忙、响应缓慢等问题。本文将探讨如何通过本地部…...
今日AI和商界事件(2025-02-05)
今日AI领域的相关事件主要包括以下几个方面: 一、DeepSeek引发全球关注 性能与成本优势: DeepSeek推出的R1模型性能出色,成本较低,在全球AI行业引发震动。该模型在数学、代码处理等方面性能优异,受到广泛赞誉。 平台…...
SQL 秒变 ER 图 sql转er图
🚀SQL 秒变 ER 图,校园小助手神了! 学数据库的宝子们集合🙋♀️ 是不是每次碰到 SQL 转 ER 图就头皮发麻?看着密密麻麻的代码,脑子直接死机,好不容易理清一点头绪,又被复杂的表关…...
SQL server 创建DB Link 详解
在日常工作中,经常涉及到跨库操作,为使跨数据库的操作变得更加灵活高效,我们可以在 SQL Server 中建立数据库链接( DB Link),实现 SQL Server 数据库与其他数据库(如 Oracle, MySQL 等ÿ…...
25.2.5学习记录
今天主要学的是哈希表的理论知识,但是都是c实现,C语言的代码实现还没有完全搞明白。 在写题的时候,懵懂的学着正确代码,用C语言模拟实现哈希表去解题。 在哈希表的理论知识中,学到哈希函数,了解哈希冲突产…...
C# List 列表综合运用实例⁓Hypak原始数据处理编程小结
C# List 列表综合运用实例⁓Hypak原始数据处理编程小结 1、一个数组解决很麻烦引出的问题1.1、RAW 文件尾部数据如下:1.2、自定义标头 ADD 或 DEL 的数据结构如下: 2、程序 C# 源代码的编写和剖析2.1、使用 ref 关键字,通过引用将参数传递,以…...
不可信的搜索路径(CWE-426)
漏洞描述:程序使用关键资源时(如动态链接库、执行文件、配置文件等)没有明确的指定资源的路径,而是依赖操作系统去搜索资源,这种行为可能被攻击者利用,通过在搜索优先级较高的目录放置不良资源,…...
Unity 2D实战小游戏开发跳跳鸟 - 记录显示最高分
上一篇文章中我们实现了游戏的开始界面,在开始界面中有一个最高分数的UI,本文将接着实现记录最高分数以及在开始界面中显示最高分数的功能。 添加跳跳鸟死亡事件 要记录最高分,则需要在跳跳鸟死亡时去进行判断当前的分数是否是最高分,如果是最高分则进行记录,如果低于之前…...
openeuler 22.03 lts sp4 使用 cri-o 和 静态 pod 的方式部署 k8s-v1.32.0 高可用集群
前情提要 整篇文章会非常的长…可以选择性阅读,另外,这篇文章是自己学习使用的,用于生产,还请三思和斟酌 静态 pod 的部署方式和二进制部署的方式是差不多的,区别在于 master 组件的管理方式是 kubectl 还是 systemctl有 kubeadm 工具,为什么还要用静态 pod 的方式部署?…...
穷举vs暴搜vs深搜vs回溯vs剪枝系列一>黄金矿工
目录 决策树:代码设计代码: 决策树: 代码设计 代码: class Solution {boolean[][] vis;int ret,m,n;public int getMaximumGold(int[][] grid) {m grid.length;n grid[0].length;vis new boolean[m][n]; for(int i 0; i <…...
SQL Server配置管理器无法连接到 WMI 提供程序
目录 第一步第二部 第一步 发现没有资源管理器 在文件夹找到管理器 打开发现报这个错误 配置管理器无法连接到 WMI 提供程序第二部 https://blog.csdn.net/thb369208315/article/details/126954074...
微信小程序获取openid和其他接口同时并发请求如何保证先获取到openid
在微信小程序中,如果你需要并发请求获取 openid 和其他接口的数据,并且希望确保先获取到 openid 之后再进行后续操作,可以考虑以下几种方法: 方法一:使用 Promise 链 1, 先请求 openid:使用 Promise 来请求 openid。 2, 在获取到 openid 后再请求其他接口。 function g…...
为AI聊天工具添加一个知识系统 之87 详细设计之28 Derivation 统一建模元模型 之1
文本要点 要点 Derivation 统一建模元模型 Derivation 统一建模元模型:意识原型的祖传代码,即支撑 程序框架的 符号学中的 自然和逻辑树。 这棵树的雏形中描述了三种建模工件:语用钩子,语法糖和语义胶水。 三种工件对应的三“…...
java进阶知识点
java回收机制 浅谈java中的反射 依赖注入的简单理解 通过接口的引用和构造方法的表达,将一些事情整好了反过来传给需要用到的地方~ 这样做得好处:做到了单一职责,并且提高了复用性,解耦了之后,任你如何实现…...