背景之前写过了,在这儿:
周末把 Selenium 的文档都过了一遍,说实话也不知道明天起床后,还记得住多少,所以写下来加强印象。
学了几个感觉比较重要的概念:
1、2 点没啥好说的,要用的时候查 api 就行了。
第 3、4 点我觉得挺关键的,后面可能要反复琢磨,贴下链接:
意思是 Selenium 不推荐我第一篇中那种写法,那样很难维护,推荐按下列原则进行开发:
我瞎翻译的,大家看原文和原文里的代码,应该会有自己的理解。
然后我根据这些原则改了下代码,如下。
贴代码前,先说一下组织架构:
+ BaseClass
+---+ TesterHomeSignInPage
+---+ TestHomeHomePage
+---+ TesterHomeTopicPage
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
from selenium import webdriver
from selenium.webdriver.common.by import By
from pages.base_class import BaseClass
from pages.testerhome_home_page import TestHomeHomePage
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
class TesterHomeSignInPage(BaseClass):
# url
_URL = "http://testerhome.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()
from selenium.webdriver.common.by import By
from pages.base_class import BaseClass
from pages.testhome_topic_page import TesterHomeTopicPage
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='/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 TesterHomeTopicPage(self.driver)
from selenium.webdriver.common.by import By
from pages.base_class import BaseClass
class TesterHomeTopicPage(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 = TesterHomeSignInPage(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()
(提交太多次话题被限制了,不上图了)
遇到挺多问题的,有的解决了,有的没解决,都和大家分享一下:
在写话题的时候,要选"节点",这儿有很多个按钮,假如我要一个个写 locators,比较麻烦,后面再想下怎么拿到全部节点,然后根据 title 去定位。(应该可以用 find_elements)
我的代码组织,体现不了 TopicPage 是 HomePage 下的一个子页面,后面再想下怎么写,可以让接盘侠一眼看出这两个 Page 的关系。
Selenium 支持在已有页面下 debug,效率会高点,链接如下:Selenium Debug
写这个 locator 的时候遇到个小坑:
_selenium_category_loc = (By.XPATH, "//a[@data-id='73']")
原本我是用 By.CSS_SELECTOR 的,但会报 InvalidSelectorException 的错误,查了资料后发现是 css 中的类名不能以数字开头导致的。
其实我按照文章里的方法也没成功,后面再查为啥了,先改用 XPATH,资料链接也贴下:
关于 CSS_SELECTOR 的 InvalidSelectorException