PytestとSeleniumを用いたメールアプリケーションUIテストフレームワークの構築

PythonとSeleniumを組み合わせたUI自動テストフレームワークの実装方法について解説します。本実装では126メールサービスを対象に、Page Objectパターンを採用した堅牢なテスト基盤を構築します。

開発環境の準備

  • Python 3.xのインストール確認
  • PyCharmとSeleniumの開発環境構築
  • 必要なライブラリのインストール:
    pip install pytest pytest-html pypiwin32 openpyxl yagmail
  • Chrome/Firefoxブラウザと対応ドライバの設定
  • メール送信用アカウントの事前設定

プロジェクト構造

PytestAutoTestFrameWork
├── config
│   ├── conf.py
│   └── config.ini
├── data
│   └── tcData.xlsx
├── Page
│   ├── BasePage.py
│   ├── ContactPage.py
│   ├── HomePage.py
│   ├── LoginPage.py
│   └── SendMailPage.py
├── report
├── TestCases
│   ├── test_contactCase.py
│   ├── test_loginCase.py
│   └── test_sendMailCase.py
├── util
│   ├── clipboard.py
│   ├── keyboard.py
│   ├── parseConFile.py
│   ├── parseExcelFile.py
│   └── sendMailForReport.py
├── conftest.py
├── pytest.ini
└── RunTestCase.py

ユーティリティモジュールの実装

添付ファイル操作のためにクリップボードとキーボード操作をシミュレートするモジュールを実装します。

クリップボード操作モジュール

class ClipBoardUtility:
    """Windowsクリップボード操作のユーティリティクラス"""
    
    @staticmethod
    def get_content():
        """クリップボード内容の取得"""
        WC.OpenClipboard()
        content = WC.GetClipboardData(win32con.CF_TEXT)
        WC.CloseClipboard()
        return content.decode('utf-8') if content else ""
    
    @staticmethod
    def set_content(text):
        """クリップボード内容の設定"""
        WC.OpenClipboard()
        WC.EmptyClipboard()
        WC.SetClipboardData(win32con.CF_UNICODETEXT, text)
        WC.CloseClipboard()

キーボード操作シミュレーション

class KeyboardSimulator:
    """キーボード操作をシミュレートするクラス"""
    
    KEY_CODES = {
        'return': 0x0D,
        'tab': 0x09,
        'control': 0x11,
        'v_key': 0x56,
        'a_key': 0x41
    }
    
    @classmethod
    def press_key(cls, key_name):
        """キーダウン操作"""
        key_name = key_name.lower()
        if key_name in cls.KEY_CODES:
            win32api.keybd_event(cls.KEY_CODES[key_name], 0, 0, 0)
    
    @classmethod
    def release_key(cls, key_name):
        """キーアップ操作"""
        key_name = key_name.lower()
        if key_name in cls.KEY_CODES:
            win32api.keybd_event(cls.KEY_CODES[key_name], 0, win32con.KEYEVENTF_KEYUP, 0)
    
    @classmethod
    def input_text(cls, text):
        """テキスト入力シミュレーション"""
        ClipBoardUtility.set_content(text)
        cls.press_key('control')
        cls.press_key('v_key')
        cls.release_key('v_key')
        cls.release_key('control')

テストデータ管理

Excelファイルからテストデータを読み込み、設定ファイルでUI要素を管理します。

Excelパーサーの実装

class ExcelParser:
    """Excelテストデータの解析クラス"""
    
    def __init__(self, file_path):
        self.workbook = load_workbook(file_path)
    
    def get_sheet_data(self, sheet_name):
        """シートデータの取得"""
        sheet = self.workbook[sheet_name]
        return [
            tuple(cell.value or "" for cell in row)
            for row in sheet.iter_rows(min_row=2)
        ]

設定ファイルの解析

class ConfigParser:
    """INI設定ファイルの解析クラス"""
    
    def __init__(self, file_path):
        self.config = configparser.ConfigParser()
        self.config.read(file_path, encoding='utf-8')
    
    def get_locator(self, section, element):
        """ロケータ情報の取得"""
        locator_str = self.config.get(section, element)
        return tuple(locator_str.split('->')) if '->' in locator_str else locator_str

Page Objectの実装

基本操作をカプセル化したベースページクラスを実装します。

class BasePage:
    """WebDriver操作の基本機能を提供する基底クラス"""
    
    def __init__(self, driver, timeout=30):
        self.driver = driver
        self.timeout = timeout
        self.wait = WebDriverWait(driver, timeout)
    
    def find_element(self, locator):
        """要素の検索(表示待ち込み)"""
        by, value = locator
        return self.wait.until(
            EC.presence_of_element_located((getattr(By, by.upper()), value))
        )
    
    def input_text(self, locator, text):
        """テキスト入力操作"""
        element = self.find_element(locator)
        element.clear()
        element.send_keys(text)
    
    def click_element(self, locator):
        """要素クリック操作"""
        element = self.wait.until(
            EC.element_to_be_clickable(self._convert_locator(locator))
        )
        element.click()
    
    def switch_to_frame(self, locator):
        """フレーム切り替え操作"""
        self.wait.until(
            EC.frame_to_be_available_and_switch_to_it(self._convert_locator(locator))
        )

テストケースの実装

ログイン機能のテストケースを例に実装方法を示します。

class TestLoginFunction:
    """126メールのログイン機能テスト"""
    
    @pytest.mark.parametrize("username,password,expected", 
        ExcelParser(DATA_FILE).get_sheet_data('login'))
    def test_login_process(self, driver, username, password, expected):
        """ログインフローの検証"""
        login_page = LoginPage(driver)
        login_page.open()
        login_page.enter_credentials(username, password)
        
        if expected == "ログイン成功":
            assert login_page.is_logged_in()
        else:
            assert login_page.get_error_message() == expected

テスト実行とレポート生成

pytestのフック機能を活用して失敗時スクリーンショットを自動添付します。

@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item):
    """テスト失敗時のスクリーンショット自動取得"""
    outcome = yield
    report = outcome.get_result()
    
    if report.when == 'call' and report.failed:
        screenshot = driver.get_screenshot_as_base64()
        report.extra.append(
            pytest_html.extras.image(screenshot, '失敗スクリーンショット')
        )

テスト実行スクリプト

一括実行とレポート送信を自動化します。

def execute_tests():
    """テスト実行とレポート送信のワークフロー"""
    pytest_args = [
        'pytest',
        '-v',
        '--html=report/test_report.html',
        '--self-contained-html'
    ]
    subprocess.run(pytest_args)
    
    # レポートをメールで送信
    mail_sender = ReportMailer(
        SMTP_SERVER,
        SENDER_EMAIL,
        SENDER_PASSWORD,
        RECIPIENTS
    )
    mail_sender.send('自動テストレポート', 'テスト結果の詳細は添付ファイルをご覧ください')

タグ: UIテスト自動化 PageObjectパターン pytest-html テストデータ駆動 メール添付テスト

6月14日 00:27 投稿