背景
背景之前写过了,在这儿:
- 记录用 selenium 把部分业务实现自动化测试(一)
目标
- 登录 玩技博客
- 发表一个新话题,并保存为草稿
备注
周末把 Selenium 的文档都过了一遍,说实话也不知道明天起床后,还记得住多少,所以写下来加强印象。文章源自玩技e族-https://www.playezu.com/185346.html
正文
学了几个感觉比较重要的概念:文章源自玩技e族-https://www.playezu.com/185346.html
- Wait
- Actions
- Domain specific language (DSL)
- Page object models (POM)
1、2 点没啥好说的,要用的时候查 api 就行了。
第 3、4 点我觉得挺关键的,后面可能要反复琢磨,贴下链接:文章源自玩技e族-https://www.playezu.com/185346.html
- Selenium DSL
- Selenium POM
意思是 Selenium 不推荐我第一篇中那种写法,那样很难维护,推荐按下列原则进行开发:文章源自玩技e族-https://www.playezu.com/185346.html
- DomainDrivenDesign: Express your tests in the language of the end-user of the app. (根据用户的行为定义你的用例)
- PageObjects: A simple abstraction of the UI of your web app. (把软件的 UI 抽象成一个页面)
- LoadableComponent: Modeling PageObjects as components. (把 PageObject 当作一个组件)
- BotStyleTests: Using a command-based approach to automating tests, rather than the object-based approach that PageObjects encourage. (建立一些通用的底层操作方法)
我瞎翻译的,大家看原文和原文里的代码,应该会有自己的理解。
然后我根据这些原则改了下代码,如下。文章源自玩技e族-https://www.playezu.com/185346.html
代码
贴代码前,先说一下组织架构:文章源自玩技e族-https://www.playezu.com/185346.html
+ BaseClass
+---+ 玩技博客SignInPage
+---+ TestHomeHomePage
+---+ 玩技博客TopicPage
base_class.py
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
class BaseClass:
def __init__(self, driver: webdriver):
self.driver = driver
# self.driver = webdriver.Chrome()
def open_page(self, url):
self.driver.get(url)
def get_element(self, locator):
wait = WebDriverWait(self.driver, timeout=3)
return wait.until(EC.visibility_of_element_located(locator))
def click_element(self, locator):
self.get_element(locator).click()
def input_element(self, locator, text):
self.get_element(locator).send_keys(text)
def get_text(self, locator):
return self.get_element(locator).text
玩技博客_login_page.py
from selenium import webdriver
from selenium.webdriver.common.by import By
from pages.base_class import BaseClass
from pages.玩技博客_home_page import TestHomeHomePage
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
class 玩技博客SignInPage(BaseClass):
# url
_URL = "http://玩技博客.com/"
# locators
_pre_login_btn_loc = (By.CSS_SELECTOR, "a.btn.btn-primary.btn-jumbotron.btn-lg")
_account_input_loc = (By.ID, "user_login")
_password_input_loc = (By.ID, "user_password")
_login_btn_loc = (By.CSS_SELECTOR, "input.btn.btn-primary.btn-lg.btn-block")
''' actions '''
def pre_login(self):
self.open_page(self._URL)
self.click_element(self._pre_login_btn_loc)
return self
def type_username(self, username: str):
self.input_element(self._account_input_loc, username)
return self
def type_password(self, password: str):
self.input_element(self._password_input_loc, password)
return self
def submit_login(self):
self.click_element(self._login_btn_loc)
return TestHomeHomePage(self.driver)
''' behaviors '''
def login_as(self, username: str, password: str):
self.pre_login()
.type_username(username)
.type_password(password)
return self.submit_login()
玩技博客_home_page.py
from selenium.webdriver.common.by import By
from pages.base_class import BaseClass
from pages.testhome_topic_page import 玩技博客TopicPage
class TestHomeHomePage(BaseClass):
# locators
_user_navbar_loc = (By.ID, "navbar-user-menu")
_menubar_loc = (By.ID, "navbar-new-menu")
_post_btn_loc = (By.XPATH, "//a[@href='http://testerhome.com/topics/new']")
''' actions '''
def exist(self):
return self.get_element(self._user_navbar_loc).is_displayed()
def post_new_topic(self):
self.click_element(self._menubar_loc)
self.click_element(self._post_btn_loc)
return self
''' behaviors '''
def post_topic(self):
self.post_new_topic()
return 玩技博客TopicPage(self.driver)
玩技博客_topic_page.py
from selenium.webdriver.common.by import By
from pages.base_class import BaseClass
class 玩技博客TopicPage(BaseClass):
# locators
_title_input_loc = (By.ID, "topic_title")
_category_btn_loc = (By.ID, "node-selector-button")
_content_input_loc = (By.ID, "topic_body")
_draft_btn_loc = (By.ID, "save_as_draft")
_save_btn_loc = (By.CSS_SELECTOR, "input.btn.btn-primary")
_title_loc = (By.XPATH, "//h1[@class='media-heading']//span[@class='title']")
# locators inside category
_selenium_category_loc = (By.XPATH, "//a[@data-id='73']") # 要研究一下怎么把所有节点都获取到,然后根据title使用,不然我要写几十个locators
# flags
_is_draft = True
''' actions '''
def write_title(self, title: str):
self.input_element(self._title_input_loc, title)
return self
def choose_category(self, category: str):
self.click_element(self._category_btn_loc) # 要研究一下怎么把所有节点都获取到,然后根据title使用
self.click_element(self._selenium_category_loc)
return self
def write_content(self, content: str):
self.input_element(self._content_input_loc, content)
return self
def save_as_draft(self):
self.click_element(self._draft_btn_loc)
return self
def submit_topic(self):
self.click_element(self._save_btn_loc)
return self
def get_title(self):
return self.get_text(self._title_loc)
''' behaviors '''
def post_topic(self, title: str, category: str, content: str, _is_draft=True):
self.write_title(title)
.choose_category(category)
.write_content(content)
.save_as_draft() if _is_draft else self.submit_topic()
测试
if __name__ == '__main__':
service = ChromeService(executable_path=ChromeDriverManager().install())
driver = webdriver.Chrome(service=service)
sign_in_page = 玩技博客SignInPage(driver)
home_page = sign_in_page.login_as("xxxx@foxmail.com", "xxxx")
assert home_page.exist()
topic_page = home_page.post_topic()
topic_testdata = {
"title": "记录用 selenium 把部分业务实现自动化测试(二)",
"category": "Selenium",
"content": "7月17日晴n今天好热,我多希望有朝一日能有人对我说:宝,早安。”,而不是”早,保安。“"
}
topic_page.post_topic(**topic_testdata)
assert topic_page.get_title() == topic_testdata["title"]
driver.quit()
效果图
(提交太多次话题被限制了,不上图了)文章源自玩技e族-https://www.playezu.com/185346.html
发现问题
遇到挺多问题的,有的解决了,有的没解决,都和大家分享一下:文章源自玩技e族-https://www.playezu.com/185346.html
在写话题的时候,要选"节点",这儿有很多个按钮,假如我要一个个写 locators,比较麻烦,后面再想下怎么拿到全部节点,然后根据 title 去定位。(应该可以用 find_elements)
文章源自玩技e族-https://www.playezu.com/185346.html我的代码组织,体现不了 TopicPage 是 HomePage 下的一个子页面,后面再想下怎么写,可以让接盘侠一眼看出这两个 Page 的关系。文章源自玩技e族-https://www.playezu.com/185346.html
Selenium 支持在已有页面下 debug,效率会高点,链接如下:Selenium Debug
写这个 locator 的时候遇到个小坑:
_selenium_category_loc = (By.XPATH, "//a[@data-id='73']")
原本我是用 By.CSS_SELECTOR 的,但会报 InvalidSelectorException 的错误,查了资料后发现是 css 中的类名不能以数字开头导致的。
其实我按照文章里的方法也没成功,后面再查为啥了,先改用 XPATH,资料链接也贴下:
关于 CSS_SELECTOR 的 InvalidSelectorException
计划
- 下周回公司后,尝试把手上的需求写成自动化
- 去 Github 上看下别人写的 Selenium 是怎样的
未知地区 1F
全部节点感觉可以整个方法遍历,如果是表结构,就遍历表格取 text 判断建议学习下Seldom & poium