Python 自動テストツール Selenium 使用ガイド

 

概要

seleniumこれは、Web アプリケーションで最も人気のある自動テスト ツールであり、自動テストやブラウザ クローラーに使用できます。公式 Web サイトのアドレスは、seleniumです。別の Web 自動テスト ツール QTP と比較すると、次の利点があります。

  • 無料のオープンソースで軽量なさまざまな言語には、小さな依存関係パッケージのみが必要です
  • Windows、Mac、Linux を含む複数のシステムをサポート
  • Chrome、FireFox、IE、safari、opera などの複数のブラウザをサポートします。
  • Java、C、Python、C#、その他の主流言語を含む複数の言語をサポート
  • 分散テストケース実行のサポート

Python+Selenium環境のインストール

まず、Python (3.7 以降を推奨) 環境をインストールしてから、pip install seleniumインストールの依存関係パッケージを直接使用する必要があります。

さらに、webdriverブラウザの対応するドライバをダウンロードする必要があります。ダウンロードしたドライバのバージョンはブラウザのバージョンと一致する必要があることに注意してください

 

ダウンロード後、ドライバーを環境変数に追加できるため、使用時にドライバーのパスを手動で指定する必要がありません。

Seleniumでブラウザを起動します

次のコードを使用してPython でChromeブラウザを起動し、ブラウザの動作を制御したり、データを読み取ることができます。

from selenium import webdriver
 
# 启动Chrome浏览器,要求chromedriver驱动程序已经配置到环境变量
# 将驱动程序和当前脚本放在同一个文件夹也可以
driver = webdriver.Chrome()
 
# 手动指定驱动程序路径
driver = webdriver.Chrome(r'D:/uusama/tools/chromedriver.exe')
 
driver = webdriver.Ie()        # Internet Explorer浏览器
driver = webdriver.Edge()      # Edge浏览器
driver = webdriver.Opera()     # Opera浏览器
driver = webdriver.PhantomJS()   # PhantomJS
 
driver.get('http://uusama.com')  # 打开指定路径的页面

起動時に起動パラメータを設定することもできます。たとえば、次のコードは起動時にエージェントを追加し、https証明書の検証を無視します。

from selenium import webdriver
 
# 创建chrome启动选项对象
options = webdriver.ChromeOptions()
 
 
options.add_argument("--proxy-server=127.0.0.1:16666")  # 设置代理
options.add_argument("---ignore-certificate-errors")  # 设置忽略https证书校验
options.add_experimental_option("excludeSwitches", ["enable-logging"])  # 启用日志
 
# 设置浏览器下载文件时保存的默认路径
prefs = {"download.default_directory": get_download_dir()}
options.add_experimental_option("prefs", prefs)
driver = webdriver.Chrome(options=options)

 いくつかの非常に便利な起動オプションを以下で使用しますoptions = webdriver.ChromeOptions()

  • options.add_argument("--proxy-server=127.0.0.1:16666"): プロキシを設定します。mitmproxy と組み合わせてパケットなどをキャプチャできます。
  • option.add_experimental_option('excludeSwitches', ['enable-automation']): Selenium 検出をバイパスするように設定します
  • options.add_argument("---ignore-certificate-errors"): https 証明書の検証を無視するように設定します
  • options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2}): ページの読み込みを高速化するために、リクエストなし画像モードを設定します。
  • chrome_options.add_argument('--headless'): ヘッドレスブラウザを設定します

Selenium ページの読み込み待機と検出

Selenium を使用してページを開いた後、すぐに操作することはできず、保留中のページ要素の読み込みが完了するまで待つ必要があり、この時点でページ要素の読み込みを検出して待つ必要があります。

time.sleep() を使用して待機します

最も簡単な方法は、time.sleep() を使用してページを開いてから一定時間待機することですが、このメソッドでは固定の待機時間しか設定できません。事前にページが読み込まれていると、無駄にブロックされてしまいます。

from time import sleep
from selenium import webdriver
 
driver = webdriver.Chrome()
driver.get('http://uusama.con')
time.sleep(10)
print('load finish')

 最大待ち時間を設定するには implicitly_wait を使用します
。また、implicitly_wait を使用して最大待ち時間を設定することもできます。ページが指定された時間内にロードされるか、タイムアウトになった場合、次のステップが実行されます。このメソッドは、すべてのリソースがロードされるまで待機します。つまり、次のステップを実行する前に、ブラウザーのタブ バーのロード チャートが回転しません。ページ要素は読み込まれているものの、js や画像などのリソースはまだ読み込まれていない可能性があるため、この時点では待つ必要があります。

また、implicitly_wait の使用は 1 回設定するだけで済み、ドライバーのライフサイクル全体で機能するため、ページの読み込み中は常にブロックされることにも注意してください。

例は次のとおりです。

from selenium import webdriver
 
driver = webdriver.Chrome()
driver.implicitly_wait(30)   # 设置最长等30秒
driver.get('http://uusama.com')
print(driver.current_url)
 
driver.get('http://baidu.com')
print(driver.current_url)

WebDriverWait を使用して待機条件を設定する
WebDriverWait (selenium.webdriver.support.wait.WebDriverWait) を使用すると、待機時間をより正確かつ柔軟に設定できます WebDriverWait は、設定した時間内に一定の条件が満たされるかどうかを検出できます。次のステップでは、設定時間を超えて満たされない場合、TimeoutException がスローされ、メソッド宣言は次のようになります。

WebDriverWait(ドライバー、タイムアウト、ポーリング頻度=0.5、無視された例外=なし)

パラメータの意味は次のとおりです。

  • driver: ブラウザドライバ
  • timeout: 最大タイムアウト期間。デフォルトは秒単位です。
  • poll_frequency: 検出間隔 (ステップ サイズ)、デフォルトは0.5秒
  • ignored_exceptionsuntil():または の呼び出しuntil_not()中に指定された例外がスローされても中断されない、無視された例外。

WebDriverWait()通常、until()orメソッドとともに使用され、戻り値がor にuntil_not()なるまでブロックを待機することを意味します。これら 2 つのメソッドのパラメータは呼び出し可能なオブジェクト、つまりメソッド名である必要があることに注意してください。このメソッドはモジュール内で使用できます。または自分でカプセル化したメソッド。TrueFalseexpected_conditions

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
 
driver = webdriver.Chrome()
driver.get("http://baidu.com")
 
# 判断id为`input`的元素是否被加到了dom树里,并不代表该元素一定可见,如果定位到就返回WebElement
element = WebDriverWait(driver, 5, 0.5).until(expected_conditions.presence_of_element_located((By.ID, "s_btn_wr")))
 
# implicitly_wait和WebDriverWait都设置时,取二者中最大的等待时间
driver.implicitly_wait(5)
 
# 判断某个元素是否被添加到了dom里并且可见,可见代表元素可显示且宽和高都大于0
WebDriverWait(driver,10).until(EC.visibility_of_element_located((By.ID, 'su')))
 
# 判断元素是否可见,如果可见就返回这个元素
WebDriverWait(driver,10).until(EC.visibility_of(driver.find_element(by=By.ID, value='kw')))

expected_conditions一般的に使用されるいくつかの方法 を以下に示します。

  • title_is: 現在のページのタイトルが期待どおりであるかどうかを判断します。
  • title_contains: 現在のページのタイトルに予期される文字列が含まれているかどうかを判断します。
  • present_of_element_located: 要素が dom ツリーに追加されているかどうかを判断することは、その要素が可視である必要があることを意味するものではありません
  • Visibility_of_element_located: 要素が表示されるかどうかを判断します (要素は非表示ではなく、要素の幅と高さが 0 に等しくありません)。
  • Visibility_of: 上記のメソッドと同じことを行います。ただし、上記のメソッドはロケーターに渡す必要があり、このメソッドは位置決めされた要素を直接渡すことができます。
  • present_of_all_elements_located: 少なくとも 1 つの要素が dom ツリーに存在するかどうかを判断します。たとえば、クラスが「column-md-3」であるページに n 個の要素がある場合、要素が 1 つある限り、このメソッドは True を返します。
  • text_to_be_present_in_element: 要素内のテキストに予期される文字列が含まれているかどうかを判断します。
  • text_to_be_present_in_element_value: 要素内の value 属性に予期される文字列が含まれているかどうかを判断します。
  • Frame_to_be_available_and_switch_to_it: フレームを切り替えられるかどうかを判断し、切り替えられる場合は True を返して切り替え、それ以外の場合は False を返します。
  • invisibility_of_element_located: 要素が dom ツリーに存在しないか、または非表示であるかを判断します。
  • element_to_be_clickable: 要素が表示され有効になっているかどうかを判断し、その要素がクリッカブルと呼ばれます。
  • staleness_of: 要素が dom ツリーから削除されるのを待ちます。このメソッドも True または False を返すことに注意してください。
  • element_to_be_selected: 要素が選択されているかどうかを決定します。通常はドロップダウン リストで使用されます。
  • element_selection_state_to_be: 要素の選択された状態が期待を満たすかどうかを判断します
  • element_located_selection_state_to_be: 上記のメソッドと同じですが、上記のメソッドは位置決めされた要素を渡し、このメソッドはロケーターを渡す点が異なります。
     

ドキュメントが読み込まれているかどうかを確認する

さらに、 driver.execute_script('return document.readyState;') == 'complete' を使用して、ドキュメントがロードされているかどうかを検出できます。

ドキュメントの読み込みが完了しましたが、これには dom を動的にレンダリングするための非同期読み込み ajax リクエストは含まれていないことに注意してください。これには、要素がレンダリングされたかどうかを検出するために WebDriverWait を使用する必要があります。

Selenium 要素の位置決めと読み取り

要素の検索

Selenium は、Chrome の要素へのアクセスを容易にする一連の API を提供します。これらの API はすべて、次WebElementのようなオブジェクトまたはリストを返します。

  • find_element_by_id(id): ID に一致する最初の要素を検索します。
  • find_element_by_class_name(): クラスに一致する最初の要素を検索します。
  • find_elements_by_xpath(): xpath に一致するすべての要素を検索します
  • find_elements_by_css_selector(): CSS セレクターに一致するすべての要素を検索します

実際、WebDriverクラス内の実装ソース コードを見ることができます。その中心となる実装は、次の 2 つの基本関数を呼び出すことです。 

  • find_element(self, by=By.ID, value=None): マッチング戦略の最初の要素を見つけます
  • find_elements(self, by=By.ID, value=None): ポリシーに一致するすべての要素を検索します

 パラメータには、、、 など by指定できます。 以下に簡単な例をいくつか示します。IDCSS_SELECTORCLASS_NAMEXPATH

  • xpath を介してテキスト ログインを含む最初の要素をクエリします: find_element_by_xpath("//*[contains(text(),'login')]")
  • クラス名refreshを含むすべての要素をクエリします: find_elements_by_class_name('refresh')
  • テーブルの 2 行目を次の形式でクエリします: find_element_by_css_selector('table tbody > tr:nth(2)')

dom要素の相互作用

上記で紹介した要素検索結果オブジェクトの場合WebElement、一般的に使用される API は次のとおりです。

  • element.text: 要素のテキスト コンテンツ (すべての子孫ノードのコンテンツを含む) を返します。要素が display=none の場合、空の文字列が返されることに注意してください。
  • element.screenshot_as_png: 要素のスクリーンショット
  • element.send_keys("input"): 要素入力ボックスの入力 入力文字列
  • element.get_attribute('data-v'): data-v name 属性値を取得します。カスタム ノード属性に加えて、textContent などの属性も取得できます。
  • element.is_displayed(): 要素がユーザーに表示されるかどうかを判断します
  • element.clear(): 要素のテキストをクリアします
  • element.click(): 要素をクリックします。要素がクリック可能でない場合は、ElementNotInteractableException がスローされます。
  • element.submit(): フォームの送信をシミュレートします

検索要素の失敗の処理

指定した要素が見つからない場合は NoSuchElementException がスローされますが、display=none の要素が取得でき、dom ノード内のすべての要素が取得できることに注意してください。

実際に使用するときは、ポーリングまたは監視が必要な場合がある、js コードによって動的に作成されるいくつかの要素に注意する必要があります。

指定した要素の存在を確認するメソッドは次のとおりです。

def check_element_exists(xpath):
    try:
        driver.find_element_by_xpath(xpath)
    except NoSuchElementException:
        return False
    return True

Selenium インタラクティブ制御

ActionChainsアクションチェーン

Webdriver は、アクション リンク キューを表すオブジェクトを通じてユーザー操作をシミュレートします。すべての操作は順番にキューに入りますが、メソッドが呼び出されるActionChainsまですぐには実行されません。perform()一般的に使用される方法は次のとおりです。

  • click(on_element=None): マウスの左ボタンをクリックします。
  • click_and_hold(on_element=None): マウスの左ボタンを放さずにクリックします。
  • context_click(on_element=None): マウスの右ボタンをクリックします。
  • double_click(on_element=None): マウスの左ボタンをダブルクリックします。
  • send_keys(*keys_to_send): 現在フォーカスされている要素にキーを送信します。
  • send_keys_to_element(element, *keys_to_send): 指定された要素にキーを送信します
  • key_down(value, element=None): キーボードのキーを押します。
  • key_up(value, element=None): キーを放します
  • rag_and_drop(source, target): 要素にドラッグして放します
  • rag_and_drop_by_offset(source, xoffset, yoffset): 特定の座標までドラッグして放します
  • move_by_offset(xoffset, yoffset): マウスを現在位置から特定の座標に移動します。
  • move_to_element(to_element): マウスを要素に移動します。
  • move_to_element_with_offset(to_element, xoffset, yoffset): 要素までの移動距離 (左上隅の座標)
  • Perform(): チェーン内のすべてのアクションを実行します。
  • release(on_element=None): 要素の位置でマウスの左ボタンを放します。

マウスイベントをシミュレートする

次のコードは、マウスの移動、クリック、ドラッグなどの操作をシミュレートします。操作中は一定時間待機する必要があることに注意してください。待機しないと、ページをレンダリングする時間がなくなります。

 

from time import sleep
from selenium import webdriver
# 引入 ActionChains 类
from selenium.webdriver.common.action_chains import ActionChains
 
driver = webdriver.Chrome()
driver.get("https://www.baidu.cn")
action_chains = ActionChains(driver)
 
target = driver.find_element_by_link_text("搜索")
# 移动鼠标到指定元素然后点击
action_chains.move_to_element(target).click(target).perform()
time.sleep(2)
 
# 也可以直接调用元素的点击方法
target.click()
time.sleep(2)
 
# 鼠标移动到(10, 50)坐标处
action_chains.move_by_offset(10, 50).perform()
time.sleep(2)
 
# 鼠标移动到距离元素target(10, 50)处
action_chains.move_to_element_with_offset(target, 10, 50).perform()
time.sleep(2)
 
# 鼠标拖拽,将一个元素拖动到另一个元素
dragger = driver.find_element_by_id('dragger')
action.drag_and_drop(dragger, target).perform()
time.sleep(2)
 
# 也可以使用点击 -> 移动来实现拖拽
action.click_and_hold(dragger).release(target).perform()
time.sleep(2)
action.click_and_hold(dragger).move_to_element(target).release().perform()

キーボード入力イベントをシミュレートする

send_keys を通じてキーボード イベントをシミュレートします。一般的に使用されるのは次のとおりです。

  • send_keys(Keys.BACK_SPACE): キー (BackSpace) を削除します。
  • send_keys(Keys.SPACE): スペースバー (スペース)
  • send_keys(Keys.TAB): タブキー(Tab)
  • send_keys(Keys.ESCAPE): 回退键(Esc)
  • send_keys(Keys.ENTER): エンターキー(Enter)
  • send_keys(Keys.F1): キーボード F1
  • send_keys(Keys.CONTROL,'a'): すべて選択 (Ctrl+A)
  • send_keys(Keys.CONTROL,'c'): コピー (Ctrl+C)
  • send_keys(Keys.CONTROL,'x'): 切り取り (Ctrl+X)
  • send_keys(Keys.CONTROL,'v'): 貼り付け (Ctrl+V)

例: 入力ボックスを見つけて内容を入力します

# 输入框输入内容
driver.find_element_by_id("kw").send_keys("uusamaa")
 
# 模拟回车删除多输入的一个字符a
driver.find_element_by_id("kw").send_keys(Keys.BACK_SPACE)

アラートボックスの処理

alert通話によってポップアップされたアラート ボックスを処理するために使用されます。

  • driver.switch_to_alert(): アラートボックスに切り替える
  • text:alert/confirm/promptのテキスト情報を返します。たとえば、jsがalert('failed')を呼び出すと、失敗した文字列が取得されます。
  • accept(): 既存のアラートボックスを受け入れます
  • dismiss(): 既存の警告ボックスを閉じます。
  • send_keys(keysToSend): アラート ボックスにテキストを送信します。

Selenium ブラウザ コントロール

基本的な共通API

非常に便利なブラウザ制御 API をいくつか以下に示します。

  • driver.current_url: 現在アクティブなウィンドウの URL を取得します。
  • driver.switch_to_window("windowName"): 指定したタブウィンドウに移動します
  • driver.switch_to_frame("frameName"): 指定した名前の iframe に移動します
  • driver.switch_to_default_content(): デフォルトのテキストコンテンツ領域に移動します。
  • driver.maximize_window(): ブラウザの表示を最大化します
  • driver.set_window_size(480, 800): 表示用にブラウザの幅を 480 に、高さを 800 に設定します。
  • driver.forword()、driver.back(): ブラウザを前後に進める
  • driver.refresh(): ページを更新します。
  • driver.close(): 現在のタブを閉じる
  • driver.quiit(): ブラウザ全体を閉じる
  • driver.save_screenshot('screen.png'): ページのスクリーンショットを保存します。
  • driver.maximize_window(): ブラウザの表示を最大化します
  • browser.execute_script('return document.readyState;'): JS スクリプトを実行します
     

Selenium は Cookie を読み取ってロードします

を使用するget_cookiesadd_cookie、Cookie をローカルにキャッシュし、起動時にロードできるため、ログイン状態を保存できます。実装は次のとおりです

import os
import json
from selenium import webdriver
 
driver = webdriver.Chrome()
driver.get("https://www.baidu.cn")
 
# 读取所有cookie并保存到文件
cookies = driver.get_cookies()
cookie_save_path = 'cookie.json'
with open(cookie_save_path, 'w', encoding='utf-8') as file_handle:
    json.dump(cookies, file_handle, ensure_ascii=False, indent=4)
 
# 从文件读取cookie并加载到浏览器
with open(cookie_save_path, 'r', encoding='utf-8') as file_handle:
    cookies = json.load(file_handle)
    for cookie in cookies:
        driver.add_cookie(cookie)

 

Selenium は新しいタブ ウィンドウを開きます

driver.get(url) を使用すると、デフォルトで最初のタブ ウィンドウで指定された接続が開き、ページ内の _blank リンクをクリックすると新しいタブ ウィンドウも開きます。

次のメソッドを使用して、指定したページのタブ ウィンドウを手動で開くこともできます。新しいウィンドウを開いた後、または閉じた後、現在アクティブなタブ ウィンドウを切り替えるには手動で switch_to.window を呼び出す必要があることに注意してください。そうしないと、NoSuchWindowException がスローされます。 。

from selenium import webdriver
 
driver = webdriver.Chrome()
driver.get("https://www.baidu.cn")
 
new_tab_url = 'http://uusama.com'
driver.execute_script(f'window.open("{new_tab_url}", "_blank");')
time.sleep(1)
 
# 注意:必须调用switch_to.window手动切换window,否则会找不到tab view
# 聚焦到新打开的tab页面,然后关闭
driver.switch_to.window(driver.window_handles[1])
time.sleep(2)
driver.close()   # 关闭当前窗口
 
# 手动回到原来的tab页面
driver.switch_to.window(driver.window_handles[0])
time.sleep(1)

 の使用に加えてexecute_script、新しいタブ ページを開くボタンをシミュレートすることによって、新しいタブ ウィンドウを作成することもできます。

  • driver.find_element_by_tag_name('body').send_keys(Keys.CONTROL + 't')
  • ActionChains(ドライバー).key_down(Keys.CONTROL).send_keys('t').key_up(Keys.CONTROL).perform()

Selenium の問題の記録

非表示要素のテキストコンテンツを取得する

要素が非表示の場合、つまりdisplay=nonefind_element要素は検索で見つかりますが、element.text属性を使用して要素のテキスト内容を取得できず、その値が空の文字列である場合は、次のメソッドを使用して要素を取得できます。 :

element = driver.find_element_by_id('uusama')
driver.execute_script("return arguments[0].textContent", element)
driver.execute_script("return arguments[0].innerHTML", element)
 
# 相应的也可以把隐藏的元素设置为非隐藏
driver.execute_script("arguments[0].style.display = 'block';", element)

 

ブラウザクラッシュ WebDriverException 例外処理

たとえば、Chrome でページを長時間実行すると、メモリ不足エラーが発生します。このとき、WebDriver は WebDriverException をスローします。基本的にすべての API がこの例外をスローします。このとき、それをキャッチして、特別に扱われます。

私の処理方法は、URLやCookieなどのページの基本情報を記録し、定期的にファイルに書き込み、異常を検知した場合にはブラウザを再起動してURLやCookieなどのデータを読み込むというものです。

Selenium はページリクエストデータをクロールします

driver.requests を使用したり、インターネット上のログを解析してページ リクエストを取得する方法がありますが、あまりうまく機能しないと思います。最後に、mitmproxy プロキシを使用してパケットをキャプチャし、Selenium の起動時にプロキシを入力して実現します。

proxy.py は、mitmproxy に基づいてパッケージ化されたカスタム プロキシ リクエスト処理であり、そのコードは次のとおりです。

import os
import gzip
from mitmproxy.options import Options
from mitmproxy.proxy.config import ProxyConfig
from mitmproxy.proxy.server import ProxyServer
from mitmproxy.tools.dump import DumpMaster
from mitmproxy.http import HTTPFlow
from mitmproxy.websocket import WebSocketFlow
 
 
class ProxyMaster(DumpMaster):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
 
    def run(self, func=None):
        try:
            DumpMaster.run(self, func)
        except KeyboardInterrupt:
            self.shutdown()
 
 
def process(url: str, request_body: str, response_content: str):
    # 抓包请求处理,可以在这儿转存和解析数据
    pass
 
 
class Addon(object):
    def websocket_message(self, flow: WebSocketFlow):
        # 监听websockt请求
        pass
 
    def response(self, flow: HTTPFlow):
        # 避免一直保存flow流,导致内存占用飙升
        # flow.request.headers["Connection"] = "close"
        # 监听http请求响应,并获取请求体和响应内容
        url = flow.request.url
        request_body = flow.request
        response_content = flow.response
 
        # 如果返回值是压缩的内容需要进行解压缩
        if response_content.data.content.startswith(b'\x1f\x8b\x08'):
            response_content = gzip.decompress(response_content.data.content).decode('utf-8')
        Addon.EXECUTOR.submit(process, url, request_body, response_content)
 
 
def run_proxy_server():
    options = Options(listen_host='0.0.0.0', listen_port=16666)
    config = ProxyConfig(options)
    master = ProxyMaster(options, with_termlog=False, with_dumper=False)
    master.server = ProxyServer(config)
    master.addons.add(Addon())
    master.run()
 
 
if __name__ == '__main__':
    with open('proxy.pid', mode='w') as fin:
        fin.write(os.getpid().__str__())
    run_proxy_server()

mitmproxy を使用する過程で、時間の経過とともに proxy.py のメモリ使用量が急増するという問題が発生します。github の issue エリアで遭遇した人もいます。http 接続の keep-alive=true リクエストが原因であると言われています。常に保存され、解放されません。その結果、より多くのリクエストがより多くのメモリを消費し、flow.request.headers["Connection"] = "close" を追加して手動で接続を閉じます。追加した後は、ある程度は安心しましたが、根本的な解決には至っていませんでした。

最後に、proxy.pid を記述してプロキシ プロセスを記録し、メモリ リークの問題を解決するために別のプログラムで proxy.py を定期的に再起動します。

最後に、私の記事を注意深く読んでくださった皆さんに感謝します。互恵性は常に必要です。それはそれほど価値のあるものではありませんが、必要な場合はそれを取り上げることができます。

これらの資料は、[ソフトウェア テスト] の友人にとって、最も包括的かつ完全な準備倉庫となるはずです。この倉庫は、最も困難な旅を乗り越える何万人ものテスト。お役に立てれば幸いです。エンジニア   

おすすめ

転載: blog.csdn.net/hlsxjh/article/details/131838197