選択の序文 #
Selenium 自動化 + pytest テスト フレームワーク
この章が必要です
特定の Python の基本 - 少なくともクラスとオブジェクト、カプセル化の継承を理解する
セレンの基礎 - この記事ではセレンについては説明しませんが、方法がわからない場合は、自分でセレンの中国語翻訳 Web サイトにアクセスしてください。
テスト フレームワークの概要#
テスト フレームワークの利点は次のとおりです。
コードの再利用率が高い フレームワークを使用しない場合、コードの冗長性が高い
ログ、レポート、メールなどの高度な機能を組み立てることができる
要素などのデータの保守性が向上する変更する場合は、構成ファイルを更新し
、Update Flexible PageObject デザイン パターンを使用するだけで済みます
テスト フレームワークのディレクトリ全体 ディレクトリ
/ファイル 説明 Python パッケージ共通ですか
? このパッケージには、構成ファイルの読み取りなど、共通の一般クラスが格納されます。
config 設定ファイル ディレクトリ はい
logs ログ ディレクトリ
ページ selenium の場合 詳細なカプセル化は、
page_element、ページ要素の保存ディレクトリ、
page_object、ページ オブジェクトの POM 設計パターンです。これについての私の理解は、Bitter Leaf のブログから来ています。それは TestCase です。
すべてのテスト ケースのセットです
utils ツール クラスです
スクリプト スクリプト ファイル
conftest.py pytest グルー ファイル
pytest.ini pytest 設定ファイル
このような単純な枠組み構造は明らかです。
上記のことを理解したので、始めましょう!
プロジェクトでは、まず上記のフレームワーク ガイドラインに従って各ディレクトリを構築します。
__init__.py
注: Python パッケージの場合、このディレクトリを Python パッケージとして識別するためのファイルを追加する必要があります。
最初の時間管理#
まず、モジュールの多くはタイムスタンプや日付などの文字列を使用するため、最初に時刻をモジュールに個別にカプセル化します。
次に、他のモジュールがそれを呼び出せるようにします。utils
ディレクトリに新しいtimes.py
モジュールを作成します
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> time
<span style="color:#7171bf">import</span> datetime
<span style="color:#7171bf">from</span> functools <span style="color:#7171bf">import</span> wraps
<span style="color:#7171bf">def</span> <span style="color:#61aeee">timestamp</span>():
<span style="color:#98c379">"""时间戳"""</span>
<span style="color:#7171bf">return</span> time.time()
<span style="color:#7171bf">def</span> <span style="color:#61aeee">dt_strftime</span>(fmt=<span style="color:#98c379">"%Y%m"</span>):
<span style="color:#98c379">"""
datetime格式化时间
:param fmt "%Y%m%d %H%M%S
"""</span>
<span style="color:#7171bf">return</span> datetime.datetime.now().strftime(fmt)
<span style="color:#7171bf">def</span> <span style="color:#61aeee">sleep</span>(seconds=<span style="color:#d19a66">1.0</span>):
<span style="color:#98c379">"""
睡眠时间
"""</span>
time.sleep(seconds)
<span style="color:#7171bf">def</span> <span style="color:#61aeee">running_time</span>(func):
<span style="color:#98c379">"""函数运行时间"""</span>
<span style="color:#61aeee"> @wraps(func)</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">wrapper</span>(*args, **kwargs):
start = timestamp()
res = func(*args, **kwargs)
<span style="color:#7171bf">print</span>(<span style="color:#98c379">"校验元素done!用时%.3f秒!"</span> % (timestamp() - start))
<span style="color:#7171bf">return</span> res
<span style="color:#7171bf">return</span> wrapper
<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:
<span style="color:#7171bf">print</span>(dt_strftime(<span style="color:#98c379">"%Y%m%d%H%M%S"</span>))
</code></span></span>
構成ファイルの追加 #構成
ファイルは常にプロジェクトの重要な部分です。
固定情報を固定ファイルに集約
conf.py#
プロジェクト内にディレクトリ全体を管理するファイルがあるはずですが、このファイルもこの Python プロジェクト内に設定します。
プロジェクトの config ディレクトリに conf.py ファイルを作成すると、すべてのディレクトリ構成情報がこのファイルに書き込まれます。
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> os
<span style="color:#7171bf">from</span> selenium.webdriver.common.by <span style="color:#7171bf">import</span> By
<span style="color:#7171bf">from</span> utils.times <span style="color:#7171bf">import</span> dt_strftime
<span style="color:#7171bf">class</span> <span style="color:#61aeee">ConfigManager</span>(<span style="color:#61aeee">object</span>):
<span style="color:#5c6370"><em># 项目目录</em></span>
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
<span style="color:#5c6370"><em># 页面元素目录</em></span>
ELEMENT_PATH = os.path.join(BASE_DIR, <span style="color:#98c379">'page_element'</span>)
<span style="color:#5c6370"><em># 报告文件</em></span>
REPORT_FILE = os.path.join(BASE_DIR, <span style="color:#98c379">'report.html'</span>)
<span style="color:#5c6370"><em># 元素定位的类型</em></span>
LOCATE_MODE = {
<span style="color:#98c379">'css'</span>: By.CSS_SELECTOR,
<span style="color:#98c379">'xpath'</span>: By.XPATH,
<span style="color:#98c379">'name'</span>: By.NAME,
<span style="color:#98c379">'id'</span>: By.ID,
<span style="color:#98c379">'class'</span>: By.CLASS_NAME
}
<span style="color:#5c6370"><em># 邮件信息</em></span>
EMAIL_INFO = {
<span style="color:#98c379">'username'</span>: <span style="color:#98c379">'[email protected]'</span>, <span style="color:#5c6370"><em># 切换成你自己的地址</em></span>
<span style="color:#98c379">'password'</span>: <span style="color:#98c379">'QQ邮箱授权码'</span>,
<span style="color:#98c379">'smtp_host'</span>: <span style="color:#98c379">'smtp.qq.com'</span>,
<span style="color:#98c379">'smtp_port'</span>: <span style="color:#d19a66">465</span>
}
<span style="color:#5c6370"><em># 收件人</em></span>
ADDRESSEE = [
<span style="color:#98c379">'[email protected]'</span>,
]
<span style="color:#61aeee"> @property</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">log_file</span>(self):
<span style="color:#98c379">"""日志目录"""</span>
log_dir = os.path.join(self.BASE_DIR, <span style="color:#98c379">'logs'</span>)
<span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> os.path.exists(log_dir):
os.makedirs(log_dir)
<span style="color:#7171bf">return</span> os.path.join(log_dir, <span style="color:#98c379">'{}.log'</span>.<span style="color:#7171bf">format</span>(dt_strftime()))
<span style="color:#61aeee"> @property</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">ini_file</span>(self):
<span style="color:#98c379">"""配置文件"""</span>
ini_file = os.path.join(self.BASE_DIR, <span style="color:#98c379">'config'</span>, <span style="color:#98c379">'config.ini'</span>)
<span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> os.path.exists(ini_file):
<span style="color:#7171bf">raise</span> FileNotFoundError(<span style="color:#98c379">"配置文件%s不存在!"</span> % ini_file)
<span style="color:#7171bf">return</span> ini_file
cm = ConfigManager()
<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:
<span style="color:#7171bf">print</span>(cm.BASE_DIR)
</code></span></span>
注: QQ 電子メール認証コード: クリックして生成チュートリアルを表示します
この conf ファイルは、Django の settings.py ファイルの設定スタイルを模倣していますが、いくつかの違いがあります。
このファイルでは、独自のディレクトリを設定し、現在のディレクトリを表示できます。
定数名はすべて大文字、関数名は小文字という規則に従います。全体的に綺麗な印象です。
config.ini#
プロジェクトの config ディレクトリに新しい config.ini ファイルを作成し、テストする必要がある URL を一時的にそのファイルに置きます。
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-ini"><span style="color:#e06c75">[HOST]</span>
<span style="color:#d19a66">HOST</span> = https://www.baidu.com
</code></span></span>
構成ファイルの読み取り#
設定ファイルが作成されたので、次に設定ファイルを読み取って、その中の情報を使用する必要があります。
common
ディレクトリに新しいreadconfig.py
ファイルを作成します
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> configparser
<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm
HOST = <span style="color:#98c379">'HOST'</span>
<span style="color:#7171bf">class</span> <span style="color:#61aeee">ReadConfig</span>(<span style="color:#61aeee">object</span>):
<span style="color:#98c379">"""配置文件"""</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">__init__</span>(self):
self.config = configparser.RawConfigParser() <span style="color:#5c6370"><em># 当有%的符号时请使用Raw读取</em></span>
self.config.read(cm.ini_file, encoding=<span style="color:#98c379">'utf-8'</span>)
<span style="color:#7171bf">def</span> <span style="color:#61aeee">_get</span>(self, section, option):
<span style="color:#98c379">"""获取"""</span>
<span style="color:#7171bf">return</span> self.config.get(section, option)
<span style="color:#7171bf">def</span> <span style="color:#61aeee">_set</span>(self, section, option, value):
<span style="color:#98c379">"""更新"""</span>
self.config.<span style="color:#7171bf">set</span>(section, option, value)
<span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(cm.ini_file, <span style="color:#98c379">'w'</span>) <span style="color:#7171bf">as</span> f:
self.config.write(f)
<span style="color:#61aeee"> @property</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">url</span>(self):
<span style="color:#7171bf">return</span> self._get(HOST, HOST)
ini = ReadConfig()
<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:
<span style="color:#7171bf">print</span>(ini.url)
</code></span></span>
Python の組み込み configparser モジュールを使用して config.ini ファイルを読み取っていることがわかります。
URL 値を抽出するには、記述が簡単な高レベル構文 @property 属性値を使用しました。
操作ログを記録する #Log
、コード内のアクションを記録するというこの用語は誰もがよく知っているはずです。
utils ディレクトリに新しい logger.py ファイルを作成します。
このファイルは、自動テスト プロセス中にいくつかの操作ステップを記録するために使用されます。
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> logging
<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm
<span style="color:#7171bf">class</span> <span style="color:#61aeee">Log</span>:
<span style="color:#7171bf">def</span> <span style="color:#61aeee">__init__</span>(self):
self.logger = logging.getLogger()
<span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> self.logger.handlers:
self.logger.setLevel(logging.DEBUG)
<span style="color:#5c6370"><em># 创建一个handle写入文件</em></span>
fh = logging.FileHandler(cm.log_file, encoding=<span style="color:#98c379">'utf-8'</span>)
fh.setLevel(logging.INFO)
<span style="color:#5c6370"><em># 创建一个handle输出到控制台</em></span>
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
<span style="color:#5c6370"><em># 定义输出的格式</em></span>
formatter = logging.Formatter(self.fmt)
fh.setFormatter(formatter)
ch.setFormatter(formatter)
<span style="color:#5c6370"><em># 添加到handle</em></span>
self.logger.addHandler(fh)
self.logger.addHandler(ch)
<span style="color:#61aeee"> @property</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">fmt</span>(self):
<span style="color:#7171bf">return</span> <span style="color:#98c379">'%(levelname)s\t%(asctime)s\t[%(filename)s:%(lineno)d]\t%(message)s'</span>
log = Log().logger
<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:
log.info(<span style="color:#98c379">'hello world'</span>)
</code></span></span>
ターミナルでファイルを実行し、コマンド ラインの出力を確認します。
<span style="color:#596172"><span style="background-color:#ffffff"><code class=" language-shell">情報 2020-12-01 16:00:05,467 [logger.py: 38] hello world
</code></span></span>
すると、今月のログ ファイルがプロジェクト ログ ディレクトリに生成されました。
POM モデルを簡単に理解する#
以下で要素について説明しますので、最初に POM モデルを理解しましょう。
ページ オブジェクト モードには次の利点があります。
このビューは、「Selenium 自動テスト - Python 言語に基づく」からのものです。
オブジェクトを抽象化すると、開発者がテストでページ コードを変更する影響を最小限に抑えることができます。したがって、ページ オブジェクトを調整するだけで済み、テストには影響
しません。
複数のテスト ケースでテスト コードの一部を再利用できます。
コードがより読みやすく、柔軟で、保守しやすくなります。
ページ オブジェクトのパターン図
Basepage - Selenium の基本クラス。Selenium メソッドをカプセル化します。
pageelements - ページ要素。ページ要素を個別に抽出してファイルに入れます。
searchpage - ページ オブジェクト クラス。Selenium メソッドとページ要素を統合します。
testcase - pytest を使用して、統合されたメソッドのテスト ケースを作成します。上の図
から、POM モデルのアイデアを通じて、次のように設定したことがわかります。
Selenium メソッド
ページ要素
ページオブジェクト
テスト
ケース 上記 4 つのコード本体が分割されています ユースケースが少ない場合はコード量が増えますが、ユースケースが多い場合には非常に大きな意味があります。ユースケースは増加、減少。コードのメンテナンスはより直感的かつ明確になり、コードの可読性はファクトリー モードよりもはるかに向上し、コードの再利用率も大幅に向上しました。
単純に要素の配置を学ぶ #
私の日常業務では、「Xpath のコピー」を右クリックして要素をブラウザ内で直接コピーする多くの学生を見てきました。この方法で取得した要素式は、多くの場合、Webdriver で実行できるほど安定していないため、フロント エンドでのいくつかの小さな変更により、要素が見つからない場合に NoSuchElementException エラーが発生します。
したがって、実際の作業や学習では、要素の配置機能を強化し、xpath や CSS セレクターなどの比較的安定した配置構文をできる限り使用する必要があります。CSS セレクターの構文は単刀直入で理解しにくいため、初心者には不向きであり、xpath と比較して位置決め構文がいくつか欠けています。したがって、要素の配置構文として xpath を選択します。
xpath#
構文規則
初心者向けチュートリアルの xpath の概要は、XML ドキュメント内の情報を検索するための言語です。
位置決めツール
chromathの
利点 : これは、firepath に似た Chrome ブラウザ用のテスト位置決めプラグインで、試してみましたが、全体的な感触は非常に良好です。初心者に対するフレンドリーさは非常に良いです。
欠点: このプラグインをインストールするには FQ が必要です。
Katalon 記録ツール
記録されたスクリプトには、要素の配置に関する情報も含まれます。
自分で書いてください - この
利点をお勧めします: これは私が推奨する方法です。なぜなら、ある程度のレベルまで習熟すると、書く内容がより直感的で簡潔になるからです。自動テスト中に問題が発生した場合、すぐに問題を特定できます。
欠点: xpath と CSS セレクター構文をある程度蓄積する必要があり、始めるのは簡単ではありません。
ページ要素の管理#
このチュートリアルで選択したテスト アドレスは Baidu ホームページであるため、対応する要素も Baidu ホームページにあります。
プロジェクト フレームワーク設計には、要素を配置するためのファイルを保存するために特別に使用されるディレクトリ page_element があります。
さまざまな構成ファイルを比較した結果、ここでは YAML ファイル形式を選択しました。読みやすくてインタラクティブです。
page_element に新しい search.yaml ファイルを作成します。
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-yaml"><span style="color:#98c379">搜索框:</span> <span style="color:#98c379">"id==kw"</span>
<span style="color:#98c379">候选:</span> <span style="color:#98c379">"css==.bdsug-overflow"</span>
<span style="color:#98c379">搜索候选:</span> <span style="color:#98c379">"css==#form div li"</span>
<span style="color:#98c379">搜索按钮:</span> <span style="color:#98c379">"id==su"</span>
</code></span></span>
要素配置ファイルが作成されたので、このファイルを読み取る必要があります。
ディレクトリcommon
にファイルを作成しますreadelement.py
。
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> os
<span style="color:#7171bf">import</span> yaml
<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm
<span style="color:#7171bf">class</span> <span style="color:#61aeee">Element</span>(<span style="color:#61aeee">object</span>):
<span style="color:#98c379">"""获取元素"""</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">__init__</span>(self, name):
self.file_name = <span style="color:#98c379">'%s.yaml'</span> % name
self.element_path = os.path.join(cm.ELEMENT_PATH, self.file_name)
<span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> os.path.exists(self.element_path):
<span style="color:#7171bf">raise</span> FileNotFoundError(<span style="color:#98c379">"%s 文件不存在!"</span> % self.element_path)
<span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(self.element_path, encoding=<span style="color:#98c379">'utf-8'</span>) <span style="color:#7171bf">as</span> f:
self.data = yaml.safe_load(f)
<span style="color:#7171bf">def</span> <span style="color:#61aeee">__getitem__</span>(self, item):
<span style="color:#98c379">"""获取属性"""</span>
data = self.data.get(item)
<span style="color:#7171bf">if</span> data:
name, value = data.split(<span style="color:#98c379">'=='</span>)
<span style="color:#7171bf">return</span> name, value
<span style="color:#7171bf">raise</span> ArithmeticError(<span style="color:#98c379">"{}中不存在关键字:{}"</span>.<span style="color:#7171bf">format</span>(self.file_name, item))
<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:
search = Element(<span style="color:#98c379">'search'</span>)
<span style="color:#7171bf">print</span>(search[<span style="color:#98c379">'搜索框'</span>])
</code></span></span>
特別なメソッド __getitem__ を使用して、任意の属性を呼び出し、yaml 内の値を読み取ります。
このようにして、配置された要素の保存と呼び出しを実現します。
しかし、まだ疑問はあります、作成したすべての要素が間違っていないことをどのように確認できるでしょうか? 人的エラーは避けられませんが、コードを通じてファイルのレビューを実行できます。現時点ではすべての問題を発見できるわけではありません。
そこで、ファイルを作成し、スクリプト ファイル ディレクトリに Inspection.py ファイルを作成し、すべての要素の yaml ファイルを検査します。
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> os
<span style="color:#7171bf">import</span> yaml
<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm
<span style="color:#7171bf">from</span> utils.times <span style="color:#7171bf">import</span> running_time
<span style="color:#61aeee">@running_time</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">inspect_element</span>():
<span style="color:#98c379">"""检查所有的元素是否正确
只能做一个简单的检查
"""</span>
<span style="color:#7171bf">for</span> files <span style="color:#7171bf">in</span> os.listdir(cm.ELEMENT_PATH):
_path = os.path.join(cm.ELEMENT_PATH, files)
<span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(_path, encoding=<span style="color:#98c379">'utf-8'</span>) <span style="color:#7171bf">as</span> f:
data = yaml.safe_load(f)
<span style="color:#7171bf">for</span> k <span style="color:#7171bf">in</span> data.values():
<span style="color:#7171bf">try</span>:
pattern, value = k.split(<span style="color:#98c379">'=='</span>)
<span style="color:#7171bf">except</span> ValueError:
<span style="color:#7171bf">raise</span> Exception(<span style="color:#98c379">"元素表达式中没有`==`"</span>)
<span style="color:#7171bf">if</span> pattern <span style="color:#7171bf">not</span> <span style="color:#7171bf">in</span> cm.LOCATE_MODE:
<span style="color:#7171bf">raise</span> Exception(<span style="color:#98c379">'%s中元素【%s】没有指定类型'</span> % (_path, k))
<span style="color:#7171bf">elif</span> pattern == <span style="color:#98c379">'xpath'</span>:
<span style="color:#7171bf">assert</span> <span style="color:#98c379">'//'</span> <span style="color:#7171bf">in</span> value,\
<span style="color:#98c379">'%s中元素【%s】xpath类型与值不配'</span> % (_path, k)
<span style="color:#7171bf">elif</span> pattern == <span style="color:#98c379">'css'</span>:
<span style="color:#7171bf">assert</span> <span style="color:#98c379">'//'</span> <span style="color:#7171bf">not</span> <span style="color:#7171bf">in</span> value, \
<span style="color:#98c379">'%s中元素【%s]css类型与值不配'</span> % (_path, k)
<span style="color:#7171bf">else</span>:
<span style="color:#7171bf">assert</span> value, <span style="color:#98c379">'%s中元素【%s】类型与值不匹配'</span> % (_path, k)
<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:
inspect_element()
</code></span></span>
このファイルを実行します。
<span style="color:#596172"><span style="background-color:#ffffff"><code class=" language-powershell">要素の確認が完了しました。<span style="color:#d19a66">0.002</span> 秒かかりました。
</code></span></span>
ご覧のとおり、短期間のうちに、入力された YAML ファイルを確認しました。
これで、必要な基本コンポーネントが大まかに完成しました。
次に、最も重要なステップであるセレンのカプセル化を実行します。
Selenium 基本クラスをカプセル化します。 #
ファクトリ モードでは、次のように記述します。
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> time
<span style="color:#7171bf">from</span> selenium <span style="color:#7171bf">import</span> webdriver
driver = webdriver.Chrome()
driver.get(<span style="color:#98c379">'https://www.baidu.com'</span>)
driver.find_element_by_xpath(<span style="color:#98c379">"//input[@id='kw']"</span>).send_keys(<span style="color:#98c379">'selenium'</span>)
driver.find_element_by_xpath(<span style="color:#98c379">"//input[@id='su']"</span>).click()
time.sleep(<span style="color:#d19a66">5</span>)
driver.quit()
</code></span></span>
非常に単純で、シンプルで、明確です。
ドライバー オブジェクトを作成し、Baidu Web ページを開き、Selenium を検索して [検索] をクリックし、5 秒間そのままにして結果を表示し、最後にブラウザーを閉じます。
では、なぜ Selenium のメソッドをカプセル化するのでしょうか? まず第一に、上記の比較的原始的な方法は、UI インターフェイスの実際の操作がはるかに複雑であるため、基本的に毎日の UI 自動テストには適していません。要素がテストされていないのは、ネットワーク上の理由または制御上の理由による可能性があります。まだ表示されています。クリックまたは入力してください。したがって、Selenium メソッドをカプセル化し、組み込みの明示的な待機または特定の条件ステートメントを通じて安定したメソッドを構築する必要があります。さらに、Selenium メソッドをカプセル化すると、日々のコードのメンテナンスに役立ちます。
ページ ディレクトリに webpage.py ファイルを作成します。
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#98c379">"""
selenium基类
本文件存放了selenium基类的封装方法
"""</span>
<span style="color:#7171bf">from</span> selenium.webdriver.support <span style="color:#7171bf">import</span> expected_conditions <span style="color:#7171bf">as</span> EC
<span style="color:#7171bf">from</span> selenium.webdriver.support.ui <span style="color:#7171bf">import</span> WebDriverWait
<span style="color:#7171bf">from</span> selenium.common.exceptions <span style="color:#7171bf">import</span> TimeoutException
<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm
<span style="color:#7171bf">from</span> utils.times <span style="color:#7171bf">import</span> sleep
<span style="color:#7171bf">from</span> utils.logger <span style="color:#7171bf">import</span> log
<span style="color:#7171bf">class</span> <span style="color:#61aeee">WebPage</span>(<span style="color:#61aeee">object</span>):
<span style="color:#98c379">"""selenium基类"""</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">__init__</span>(self, driver):
<span style="color:#5c6370"><em># self.driver = webdriver.Chrome()</em></span>
self.driver = driver
self.timeout = <span style="color:#d19a66">20</span>
self.wait = WebDriverWait(self.driver, self.timeout)
<span style="color:#7171bf">def</span> <span style="color:#61aeee">get_url</span>(self, url):
<span style="color:#98c379">"""打开网址并验证"""</span>
self.driver.maximize_window()
self.driver.set_page_load_timeout(<span style="color:#d19a66">60</span>)
<span style="color:#7171bf">try</span>:
self.driver.get(url)
self.driver.implicitly_wait(<span style="color:#d19a66">10</span>)
log.info(<span style="color:#98c379">"打开网页:%s"</span> % url)
<span style="color:#7171bf">except</span> TimeoutException:
<span style="color:#7171bf">raise</span> TimeoutException(<span style="color:#98c379">"打开%s超时请检查网络或网址服务器"</span> % url)
<span style="color:#61aeee"> @staticmethod</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">element_locator</span>(func, locator):
<span style="color:#98c379">"""元素定位器"""</span>
name, value = locator
<span style="color:#7171bf">return</span> func(cm.LOCATE_MODE[name], value)
<span style="color:#7171bf">def</span> <span style="color:#61aeee">find_element</span>(self, locator):
<span style="color:#98c379">"""寻找单个元素"""</span>
<span style="color:#7171bf">return</span> WebPage.element_locator(<span style="color:#7171bf">lambda</span> *args: self.wait.until(
EC.presence_of_element_located(args)), locator)
<span style="color:#7171bf">def</span> <span style="color:#61aeee">find_elements</span>(self, locator):
<span style="color:#98c379">"""查找多个相同的元素"""</span>
<span style="color:#7171bf">return</span> WebPage.element_locator(<span style="color:#7171bf">lambda</span> *args: self.wait.until(
EC.presence_of_all_elements_located(args)), locator)
<span style="color:#7171bf">def</span> <span style="color:#61aeee">elements_num</span>(self, locator):
<span style="color:#98c379">"""获取相同元素的个数"""</span>
number = <span style="color:#7171bf">len</span>(self.find_elements(locator))
log.info(<span style="color:#98c379">"相同元素:{}"</span>.<span style="color:#7171bf">format</span>((locator, number)))
<span style="color:#7171bf">return</span> number
<span style="color:#7171bf">def</span> <span style="color:#61aeee">input_text</span>(self, locator, txt):
<span style="color:#98c379">"""输入(输入前先清空)"""</span>
sleep(<span style="color:#d19a66">0.5</span>)
ele = self.find_element(locator)
ele.clear()
ele.send_keys(txt)
log.info(<span style="color:#98c379">"输入文本:{}"</span>.<span style="color:#7171bf">format</span>(txt))
<span style="color:#7171bf">def</span> <span style="color:#61aeee">is_click</span>(self, locator):
<span style="color:#98c379">"""点击"""</span>
self.find_element(locator).click()
sleep()
log.info(<span style="color:#98c379">"点击元素:{}"</span>.<span style="color:#7171bf">format</span>(locator))
<span style="color:#7171bf">def</span> <span style="color:#61aeee">element_text</span>(self, locator):
<span style="color:#98c379">"""获取当前的text"""</span>
_text = self.find_element(locator).text
log.info(<span style="color:#98c379">"获取文本:{}"</span>.<span style="color:#7171bf">format</span>(_text))
<span style="color:#7171bf">return</span> _text
<span style="color:#61aeee"> @property</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">get_source</span>(self):
<span style="color:#98c379">"""获取页面源代码"""</span>
<span style="color:#7171bf">return</span> self.driver.page_source
<span style="color:#7171bf">def</span> <span style="color:#61aeee">refresh</span>(self):
<span style="color:#98c379">"""刷新页面F5"""</span>
self.driver.refresh()
self.driver.implicitly_wait(<span style="color:#d19a66">30</span>)
</code></span></span>
このファイルでは、Selenium のクリックを明示的に待機する、send_keys などのメソッドの二次カプセル化を作成しました。操作の成功率が向上しました。
さて、POM モデルの約半分が完成しました。次にページオブジェクトを入力します。
ページ オブジェクトの作成
#page_object ディレクトリに searchpage.py ファイルを作成します。
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">from</span> page.webpage <span style="color:#7171bf">import</span> WebPage, sleep
<span style="color:#7171bf">from</span> common.readelement <span style="color:#7171bf">import</span> Element
search = Element(<span style="color:#98c379">'search'</span>)
<span style="color:#7171bf">class</span> <span style="color:#61aeee">SearchPage</span>(<span style="color:#61aeee">WebPage</span>):
<span style="color:#98c379">"""搜索类"""</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">input_search</span>(self, content):
<span style="color:#98c379">"""输入搜索"""</span>
self.input_text(search[<span style="color:#98c379">'搜索框'</span>], txt=content)
sleep()
<span style="color:#61aeee"> @property</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">imagine</span>(self):
<span style="color:#98c379">"""搜索联想"""</span>
<span style="color:#7171bf">return</span> [x.text <span style="color:#7171bf">for</span> x <span style="color:#7171bf">in</span> self.find_elements(search[<span style="color:#98c379">'候选'</span>])]
<span style="color:#7171bf">def</span> <span style="color:#61aeee">click_search</span>(self):
<span style="color:#98c379">"""点击搜索"""</span>
self.is_click(search[<span style="color:#98c379">'搜索按钮'</span>])
</code></span></span>
このファイルには、検索キーワードを入力し、「検索」をクリックし、Lenovo を検索する手順がカプセル化されています。
そして設定された注釈。
コメントがないとしばらくするとコードが読みにくくなってしまうため、日常生活でコメントを書く習慣を身につけるべきです。
これで、ページ オブジェクトが完成しました。次にテストケースの作成を開始します。テストを開始する前に、pytest テスト フレームワークについて理解しましょう。
Pytest の簡単な紹介#
pytest フレームワークの公式 Web サイトを開きます。pytest: より良いプログラムを書くのに役立ちます — pytest ドキュメント
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em># content of test_sample.py</em></span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">inc</span>(x):
<span style="color:#7171bf">return</span> x + <span style="color:#d19a66">1</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">test_answer</span>():
<span style="color:#7171bf">assert</span> inc(<span style="color:#d19a66">3</span>) == <span style="color:#d19a66">5</span>
</code></span></span>
公式チュートリアルは入門書には向かないと思いますし、中国語版もありません。
Shanghai Youyou の pytest チュートリアルを参照することをお勧めします。
pytest.ini #
pytest プロジェクトの構成ファイルは、pytest の実行中の操作に対するグローバルな制御を提供できます。
プロジェクトのルート ディレクトリに新しいファイルを作成しますpytest.ini
。
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-ini"><span style="color:#e06c75">[pytest]</span>
<span style="color:#d19a66">addopts</span> = --html=report.html --self-contained-html
</code></span></span>
addopts は、実行中に他のパラメーターの説明を指定します。
--html=report/report.html --self-contained-html pytest-html スタイルのレポートを生成します。 -s
ユースケースでのデバッグ情報を出力します。 -q
静かにテストします
。 -v 出力できます。ユース ケースが配置されているファイルやユース ケースの名前など、ユース ケースの詳細な実行情報 テスト ケースの作成 #
テスト
ケースを作成するには pytest を使用します。
TestCase ディレクトリに test_search.py ファイルを作成します。
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> re
<span style="color:#7171bf">import</span> pytest
<span style="color:#7171bf">from</span> utils.logger <span style="color:#7171bf">import</span> log
<span style="color:#7171bf">from</span> common.readconfig <span style="color:#7171bf">import</span> ini
<span style="color:#7171bf">from</span> page_object.searchpage <span style="color:#7171bf">import</span> SearchPage
<span style="color:#7171bf">class</span> <span style="color:#61aeee">TestSearch</span>:
<span style="color:#61aeee"> @pytest.fixture(scope=<span style="color:#3388aa">'function'</span>, autouse=<span style="color:#56b6c2">True</span>)</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">open_baidu</span>(self, drivers):
<span style="color:#98c379">"""打开百度"""</span>
search = SearchPage(drivers)
search.get_url(ini.url)
<span style="color:#7171bf">def</span> <span style="color:#61aeee">test_001</span>(self, drivers):
<span style="color:#98c379">"""搜索"""</span>
search = SearchPage(drivers)
search.input_search(<span style="color:#98c379">"selenium"</span>)
search.click_search()
result = re.search(<span style="color:#98c379">r'selenium'</span>, search.get_source)
log.info(result)
<span style="color:#7171bf">assert</span> result
<span style="color:#7171bf">def</span> <span style="color:#61aeee">test_002</span>(self, drivers):
<span style="color:#98c379">"""测试搜索候选"""</span>
search = SearchPage(drivers)
search.input_search(<span style="color:#98c379">"selenium"</span>)
log.info(<span style="color:#7171bf">list</span>(search.imagine))
<span style="color:#7171bf">assert</span> <span style="color:#7171bf">all</span>([<span style="color:#98c379">"selenium"</span> <span style="color:#7171bf">in</span> i <span style="color:#7171bf">for</span> i <span style="color:#7171bf">in</span> search.imagine])
<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:
pytest.main([<span style="color:#98c379">'TestCase/test_search.py'</span>])
</code></span></span>
テスト用に書きました。
pytest.fixture は、単体テストのセットアップおよびティアダウンと同じ開始前およびクリーンアップ後のデコレーターを実装します。
最初のテストケース:
Baidu に Selenium キーワードを実装し、検索ボタンをクリックし、正規表現を使用して検索結果の結果ページのソース コードを検索し、返された数値が 10 より大きい場合は合格とみなされます。
2 番目のテスト ケース:
実装して Selenium を検索し、検索候補のすべての結果に Selenium キーワードが含まれるかどうかをアサートします。
最後に、以下に実行開始ステートメントを記述します。
この時点で実行に入るべきですが、まだ問題があり、ドライバーをまだ渡していません。
conftest.py#
プロジェクトのルート ディレクトリに新しい conftest.py ファイルを作成します。
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> pytest
<span style="color:#7171bf">from</span> py.xml <span style="color:#7171bf">import</span> html
<span style="color:#7171bf">from</span> selenium <span style="color:#7171bf">import</span> webdriver
driver = <span style="color:#56b6c2">None</span>
<span style="color:#61aeee">@pytest.fixture(scope=<span style="color:#3388aa">'session'</span>, autouse=<span style="color:#56b6c2">True</span>)</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">drivers</span>(request):
<span style="color:#7171bf">global</span> driver
<span style="color:#7171bf">if</span> driver <span style="color:#7171bf">is</span> <span style="color:#56b6c2">None</span>:
driver = webdriver.Chrome()
driver.maximize_window()
<span style="color:#7171bf">def</span> <span style="color:#61aeee">fn</span>():
driver.quit()
request.addfinalizer(fn)
<span style="color:#7171bf">return</span> driver
<span style="color:#61aeee">@pytest.hookimpl(hookwrapper=<span style="color:#56b6c2">True</span>)</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_runtest_makereport</span>(item):
<span style="color:#98c379">"""
当测试失败的时候,自动截图,展示到html报告中
:param item:
"""</span>
pytest_html = item.config.pluginmanager.getplugin(<span style="color:#98c379">'html'</span>)
outcome = <span style="color:#7171bf">yield</span>
report = outcome.get_result()
report.description = <span style="color:#7171bf">str</span>(item.function.__doc__)
extra = <span style="color:#7171bf">getattr</span>(report, <span style="color:#98c379">'extra'</span>, [])
<span style="color:#7171bf">if</span> report.when == <span style="color:#98c379">'call'</span> <span style="color:#7171bf">or</span> report.when == <span style="color:#98c379">"setup"</span>:
xfail = <span style="color:#7171bf">hasattr</span>(report, <span style="color:#98c379">'wasxfail'</span>)
<span style="color:#7171bf">if</span> (report.skipped <span style="color:#7171bf">and</span> xfail) <span style="color:#7171bf">or</span> (report.failed <span style="color:#7171bf">and</span> <span style="color:#7171bf">not</span> xfail):
file_name = report.nodeid.replace(<span style="color:#98c379">"::"</span>, <span style="color:#98c379">"_"</span>) + <span style="color:#98c379">".png"</span>
screen_img = _capture_screenshot()
<span style="color:#7171bf">if</span> file_name:
html = <span style="color:#98c379">'<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:1024px;height:768px;" '</span> \
<span style="color:#98c379">'onclick="window.open(this.src)" align="right"/></div>'</span> % screen_img
extra.append(pytest_html.extras.html(html))
report.extra = extra
<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_results_table_header</span>(cells):
cells.insert(<span style="color:#d19a66">1</span>, html.th(<span style="color:#98c379">'用例名称'</span>))
cells.insert(<span style="color:#d19a66">2</span>, html.th(<span style="color:#98c379">'Test_nodeid'</span>))
cells.pop(<span style="color:#d19a66">2</span>)
<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_results_table_row</span>(report, cells):
cells.insert(<span style="color:#d19a66">1</span>, html.td(report.description))
cells.insert(<span style="color:#d19a66">2</span>, html.td(report.nodeid))
cells.pop(<span style="color:#d19a66">2</span>)
<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_results_table_html</span>(report, data):
<span style="color:#7171bf">if</span> report.passed:
<span style="color:#7171bf">del</span> data[:]
data.append(html.div(<span style="color:#98c379">'通过的用例未捕获日志输出.'</span>, class_=<span style="color:#98c379">'empty log'</span>))
<span style="color:#7171bf">def</span> <span style="color:#61aeee">_capture_screenshot</span>():
<span style="color:#98c379">'''
截图保存为base64
:return:
'''</span>
<span style="color:#7171bf">return</span> driver.get_screenshot_as_base64()
</code></span></span>
conftest.py は、フィクスチャ メソッドを使用してドライバをカプセル化して渡すテスト フレームワーク pytest のグルー ファイルです。
実行ケース #
上記でフレームワーク全体とテスト ケースを作成しました。
現在のプロジェクトのホーム ディレクトリに入り、次のコマンドを実行します。
<span style="color:#596172"><span style="background-color:#ffffff"><code class=" language-powershell">pytest </code></span></span> コマンド
行
出力:
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell">Test session starts (platform: win32, Python <span style="color:#d19a66">3.7</span>.<span style="color:#d19a66">7</span>, pytest <span style="color:#d19a66">5.3</span>.<span style="color:#d19a66">2</span>, py<span style="color:#7171bf">test-sugar</span> <span style="color:#d19a66">0.9</span>.<span style="color:#d19a66">2</span>)
cachedir: .pytest_cache
metadata: {<span style="color:#98c379">'Python'</span>: <span style="color:#98c379">'3.7.7'</span>, <span style="color:#98c379">'Platform'</span>: <span style="color:#98c379">'Windows-10-10.0.18362-SP0'</span>, <span style="color:#98c379">'Packages'</span>: {<span style="color:#98c379">'pytest'</span>: <span style="color:#98c379">'5.3.2'</span>, <span style="color:#98c379">'py'</span>: <span style="color:#98c379">'1.8.0'</span>, <span style="color:#98c379">'pluggy'</span>: <span style="color:#98c379">'0.13.1'</span>}, <span style="color:#98c379">'Plugins'</span>: {<span style="color:#98c379">'forked'</span>: <span style="color:#98c379">'1.1.3'</span>, <span style="color:#98c379">'html'</span>: <span style="color:#98c379">'2.0.1'</span>, <span style="color:#98c379">'metadata'</span>: <span style="color:#98c379">'1.8.0'</span>, <span style="color:#98c379">'ordering'</span>: <span style="color:#98c379">'0.6'</span>, <span style="color:#98c379">'rerunfailures'</span>: <span style="color:#98c379">'8.0'</span>, <span style="color:#98c379">'sugar'</span>: <span style="color:#98c379">'0.9.2'</span>, <span style="color:#98c379">'xdist'</span>: <span style="color:#98c379">'1.31.0'</span>}, <span style="color:#98c379">'JAVA_HOME'</span>: <span style="color:#98c379">'D:\\Program Files\\Java\\jdk1.8.0_131'</span>}
rootdir: C:\Users\hoou\PycharmProjects\web<span style="color:#56b6c2">-demotest</span>, inifile: pytest.ini
plugins: forked<span style="color:#56b6c2">-1</span>.<span style="color:#d19a66">1.3</span>, html<span style="color:#56b6c2">-2</span>.<span style="color:#d19a66">0.1</span>, metadata<span style="color:#56b6c2">-1</span>.<span style="color:#d19a66">8.0</span>, ordering<span style="color:#56b6c2">-0</span>.<span style="color:#d19a66">6</span>, rerunfailures<span style="color:#56b6c2">-8</span>.<span style="color:#d19a66">0</span>, sugar<span style="color:#56b6c2">-0</span>.<span style="color:#d19a66">9.2</span>, xdist<span style="color:#56b6c2">-1</span>.<span style="color:#d19a66">31.0</span>
collecting ...
DevTools listening on ws://<span style="color:#d19a66">127.0</span>.<span style="color:#d19a66">0.1</span>:<span style="color:#d19a66">10351</span>/devtools/browser/<span style="color:#d19a66">78</span>bef34d<span style="color:#56b6c2">-b94c-4087-b724-34fb6b2ef6d1</span>
TestCase\test_search.py::TestSearch.test_001 ✓ <span style="color:#d19a66">50</span>% █████
TestCase\test_search.py::TestSearch.test_002 ✓ <span style="color:#d19a66">100</span>% ██████████
<span style="color:#56b6c2">-------------------------------</span> generated html file: file://C:\Users\hoou\PycharmProjects\web<span style="color:#56b6c2">-demotest</span>\report\report.html <span style="color:#56b6c2">--------------------------------</span>
Results (<span style="color:#d19a66">12.90</span>s):
<span style="color:#d19a66">2</span> passed
</code></span></span>
2 つのユースケースが正常に実行されたことがわかります。
report.html ファイルがプロジェクトのレポート ディレクトリに生成されます。
これは生成されたテスト レポート ファイルです。
電子メールの送信 #
プロジェクトの実行が完了したら、結果を表示するには自分または他の人のメールボックスに電子メールを送信する必要があります。
電子メールを送信するためのモジュールを作成します。
utils ディレクトリに新しい send_mail.py ファイルを作成します。
<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> zmail
<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm
<span style="color:#7171bf">def</span> <span style="color:#61aeee">send_report</span>():
<span style="color:#98c379">"""发送报告"""</span>
<span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(cm.REPORT_FILE, encoding=<span style="color:#98c379">'utf-8'</span>) <span style="color:#7171bf">as</span> f:
content_html = f.read()
<span style="color:#7171bf">try</span>:
mail = {
<span style="color:#98c379">'from'</span>: <span style="color:#98c379">'[email protected]'</span>,
<span style="color:#98c379">'subject'</span>: <span style="color:#98c379">'最新的测试报告邮件'</span>,
<span style="color:#98c379">'content_html'</span>: content_html,
<span style="color:#98c379">'attachments'</span>: [cm.REPORT_FILE, ]
}
server = zmail.server(*cm.EMAIL_INFO.values())
server.send_mail(cm.ADDRESSEE, mail)
<span style="color:#7171bf">print</span>(<span style="color:#98c379">"测试邮件发送成功!"</span>)
<span style="color:#7171bf">except</span> Exception <span style="color:#7171bf">as</span> e:
<span style="color:#7171bf">print</span>(<span style="color:#98c379">"Error: 无法发送邮件,{}!"</span>, <span style="color:#7171bf">format</span>(e))
<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">"__main__"</span>:
<span style="color:#98c379">'''请先在config/conf.py文件设置QQ邮箱的账号和密码'''</span>
send_report()
</code></span></span>
このファイルを実行します。
<span style="color:#596172"><span style="background-color:#ffffff"><code class=" language-shell">テストメールが正常に送信されました。
</code></span></span>
テストレポートメールが正常に送信されたことがわかります。メールボックスを開きます。
メールは正常に受信されました。
このデモプロジェクトが無事に完成しました、とてもやりがいを感じませんか?メールを送った瞬間に達成感を感じます。
最後に、pytest+selenium フレームワークを全体的に理解する必要があり、自動テストへの道で新たなレベルに到達しました。
アリュール テスト レポートの生成 #
アリュール レポートは既に別のブログで書いており、この記事のオープンソース プロジェクトを使用して修正したものなので、ここにリンクを貼っておきます^_^
pytest は、魅力的なテスト レポートを使用します - 風に揺れる - ブログパーク
オープンソースのアドレス#
学習とコミュニケーションを促進するために、このサンプル プロジェクトは Code Cloud でオープンソース化されています。
web-demotest: pytest+selenium+allure UI+POM 自動テスト フレームワーク
最後に、私の記事をよく読んでくださった皆様に感謝申し上げます。ファンの増加と注目をずっと見ていると、そこには礼儀があります。それほど価値のあるものではありませんが、活用していただければフォローしていただけます公開アカウント「プログラマー タイガー バーム」から直接奪うもの: