在前一天文章的最後,雖然把自動化完成了,但是 code 裡面的功能擠成一團,難以閱讀,所以今天會帶到 page object model 的介紹,順便把我們昨天的 code 改成 POM 的形式。
就跟他的名稱" Page Object Model"一樣,把每一個頁面拆成一個 Object ,每一個 Object 負責處理該頁面相關的動作,這樣可以把頁面邏輯獨立,實際操作只要調用 Object 即可,提高重用性之餘也增加可讀性,也符合物件導向的精神。
首先我們先回頭檢視我們的自動化經過什麼步驟:
因此我們可以把原本的檔案拆成三個 page
pages/ithelp_page.py
pages/login_page.py
pages/article_page.py
除此之外,也可把常用的 find_elements
等操作抽出來放到 base_page.py
,需要用到操作再調用,讓程式看起來更簡潔也更好維護。
pages/base_page.py
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class BasePage():
def __init__(self, browser):
self.browser = browser
self.actions = ActionChains(browser)
def find_element_visible(self, locator, timeout = 10):
"""尋找可見元素"""
return WebDriverWait(self.browser, timeout).until(
EC.presence_of_element_located(locator)
)
def find_element_clickable(self, locator, timeout = 10):
"""尋找可點擊元素"""
return WebDriverWait(self.browser, timeout).until(
EC.element_to_be_clickable(locator)
)
def click_element(self, locator):
"""點擊元素"""
element = self.find_element_clickable(locator)
element.click()
def input_text(self, locator, text):
"""輸入文字"""
element = self.find_element_visible(locator)
element.send_keys(text)
def actions_input_text(self, locator, text):
"""使用 actions 輸入文字"""
element = self.find_element_visible(locator)
self.actions.click(element).send_keys(text).perform()
這邊的 find_element
改成用 EC 與 WebDriverWait 實現, WebDriverWait 用於新增元素等待時間,在 timeout 設定的時間內會持續尋找元素。
而EC 用於把 find_element 條件細分化,例如元素是否可見,元素是否可點擊等。
再來我們可以依序把原本的 code 移到各自的 object
pages/ithelp_page.py
from selenium.webdriver.common.by import By
from pages.base_page import BasePage
class ITHelpPage(BasePage):
def __init__(self, browser):
super().__init__(browser)
self.login_page_button = (By.XPATH, "//a[@class='menu__item-link']")
self.iron_button = (By.XPATH, "//button[@class='menu__ironman-btn']")
self.select_topic_button = (By.XPATH, "//a[@class='ir-modal__list-link']")
def go_to_login_page(self):
self.click_element(self.login_page_button)
def go_to_iron_page(self):
self.click_element(self.iron_button)
self.click_element(self.select_topic_button)
pages/login_page.py
from selenium.webdriver.common.by import By
from pages.base_page import BasePage
class LoginPage(BasePage):
def __init__(self, browser):
super().__init__(browser)
self.account = (By.ID, "account")
self.password = (By.ID, "password")
self.login_button = (By.ID, "loginBtn")
def login(self, account, password):
self.input_text(self.account, account)
self.input_text(self.password, password)
self.click_element(self.login_button)
pages/article_page.py
from selenium.webdriver.common.by import By
from pages.base_page import BasePage
class ArticlePage(BasePage):
def __init__(self, browser):
super().__init__(browser)
self.subject = (By.XPATH, "//input[@class='form-control post-header__title']")
self.article = (By.CSS_SELECTOR, ".CodeMirror")
self.more_icon = (By.XPATH, "//button[contains(@class, 'btn')]/span[@class='caret']")
self.send_article = (By.XPATH, "//button[text()='發表文章']")
def post_article(self, subject, article):
self.input_text(self.subject, subject)
self.actions_input_text(self.article, article)
self.click_element(self.more_icon)
self.click_element(self.send_article)
最後再改原本的test.py
import json
import os
import time
from dotenv import load_dotenv
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from pages.ithelp_page import ITHelpPage
from pages.login_page import LoginPage
from pages.article_page import ArticlePage
def driver_setup():
# 設定 ChromeDriver 的路徑
browesr_path = "c:\Program Files\chromedriver-win64\chromedriver-win64\chromedriver.exe"
service = Service(browesr_path)
# 設定 Chrome 的啟動參數
options = Options()
# 設定瀏覽器語言為中文
options.add_argument("--lang=zh-TW")
# 全螢幕模式
options.add_argument("--kiosk")
browser = webdriver.Chrome(service=service, options=options)
return browser
# 初始化瀏覽器
browser = driver_setup()
it_help_page = ITHelpPage(browser)
login_page = LoginPage(browser)
article_page = ArticlePage(browser)
# 載入檔案
with open('irondata.json', 'r', encoding='utf-8') as file:
irondata = json.load(file)
day10_subject = irondata["subject"]
day10_article = irondata["article"]
load_dotenv()
ITHOMELOGIN = json.loads(os.getenv("ITHOMELOGIN"))
account = ITHOMELOGIN["account"]
password = ITHOMELOGIN["password"]
browser.get("https://ithelp.ithome.com.tw/")
it_help_page.go_to_login_page()
login_page.login(account, password)
it_help_page.go_to_iron_page()
article_page.post_article(day10_subject, day10_article)
time.sleep(50)
可以看到操作自動化的 code 減少很多,看起來也很直覺,要修改自動化的部分只要到對應的 page 就可以了。
明天預計會介紹 Python 的 PEP8 規範,這是 Python 社群共同的 coding 規範,可以讓我們的 code 更有可讀性且一致。
最後附上自動化專案的專案結構
└──iThelp automation
│ │
│ ├── pages
│ │ └── article_page.py
│ │ └── base_page.py
│ │ └── ithelp_page.py
│ │ └── login_page.py
│ │
│ ├── .env
│ │
│ ├── test.py
│ │
└── └── irondata.json