iT邦幫忙

2024 iThome 鐵人賽

DAY 6
0
Python

利用Python完成自動化測試專案系列 第 11

D11 Page Object Model

  • 分享至 

  • xImage
  •  

在前一天文章的最後,雖然把自動化完成了,但是 code 裡面的功能擠成一團,難以閱讀,所以今天會帶到 page object model 的介紹,順便把我們昨天的 code 改成 POM 的形式。

POM 是什麼

就跟他的名稱" Page Object Model"一樣,把每一個頁面拆成一個 Object ,每一個 Object 負責處理該頁面相關的動作,這樣可以把頁面邏輯獨立,實際操作只要調用 Object 即可,提高重用性之餘也增加可讀性,也符合物件導向的精神。

實作 POM

首先我們先回頭檢視我們的自動化經過什麼步驟:

  1. iT 邦幫忙主頁面
  2. 登入頁面
  3. iT 邦幫忙主頁面
  4. 鐵人發文頁面

因此我們可以把原本的檔案拆成三個 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

上一篇
D10 實作發文自動化(3) 完成
下一篇
D12 PEP 8 規範
系列文
利用Python完成自動化測試專案30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言