Edited at

Selene : Python にも Selenide ライクな Selenium ラッパーあります

More than 1 year has passed since last update.


はじめに

仕事では Java を主に使っている関係で、 e2e テストのために Selenide を使っている喫煙者です。

Selenide 便利です。好きです。タバコと同じくらい。

そんな私が、 Ploom TECH を購入しようとしたのですが、


  • ネット抽選当たらない

  • 店舗購入でもネットで予約が必要

  • ちょいちょい見ても予約の受付が開始されない

というような状態でした。1

そこで、お目当の店舗で予約の受付を開始したら、予約受け付けてるよというのを Slack で通知するというのを自動化することにしました。2

ただし、自宅では Java を使いたくないというのがあったので、 Selenide ライクな Selenium ラッパーが JavaScript か Python あたりでないかなと探して見たら、 Python 用の Selene というのを発見し、使ってみたので、それの簡単なご紹介です。


Selene の特徴


Selenide と同等の機能

Selenide では要素選択が jQuery のようにできたり、 Ajax でいちいち wait を書いたりする必要がないという素晴らしい特徴があるのですが、 Selenide port in Python と謳っているようにほぼ同じことができます。


WebDriver の管理不要

ブラウザを動かすための WebDriver をダウンロードしたりだとか、パスを通したりとかが不要になっています。

Selenide ではその辺のサポートは行なっていません。


Selene のインストール方法

Selene は pip でインストールできますが、この記事を書いた時点では過渡期のため、以下のインストール方法が推奨されています。

pip install selene --pre


Selene の使い方

以下、 Ploom TECH の予約可能な店舗を通知するのに利用したコードを使って、説明します。

なお、このコードは Gist にも置いてあります。

import os

import traceback

from selene import config
from selene.browsers import BrowserName
from selene.api import *

from selenium import webdriver

from slackclient import SlackClient

SLACK_TOKEN = os.environ['SLACK_TOKEN']
SLACK_CHANNEL_LOG = os.environ['SLACK_CHANNEL_LOG']
SLACK_CHANNEL_FOUND = os.environ['SLACK_CHANNEL_FOUND']
SLACK_MENTION_ID = os.environ['SLACK_MENTION_ID']
PLOOM_ACCOUNTUID = os.environ['PLOOM_ACCOUNTUID']
PLOOM_PASSWORD = os.environ['PLOOM_PASSWORD']
PLOOM_PREFECTURE = os.environ['PLOOM_PREFECTURE']
PLOOM_CITY = os.getenv('PLOOM_CITY', 'すべて選択')
PLOOM_SHOP = os.getenv('PLOOM_SHOP', None)

SLACK_METHOD = 'chat.postMessage'
PLOOM_URL = 'https://www.ploom.jp/tech/reserve/'

def main():
slack_client = SlackClient(SLACK_TOKEN)
slack_client.api_call(
SLACK_METHOD,
channel=SLACK_CHANNEL_LOG,
text='Ploom の予約を受け付けている店舗を検索しています...\n都道府県 `{0}` 市区町村 `{1}` 店舗名 `{2}`'
.format(PLOOM_PREFECTURE, PLOOM_CITY, PLOOM_SHOP)
)
try:
# 利用するブラウザを設定します。
# config は Selenide の Configuration にあたります。
config.browser_name = BrowserName.PHANTOMJS
# PhantomJSの場合にこの設定が必要になっています。
# Chrome では勝手に設定されるので不要です。
config.desired_capabilities = webdriver.DesiredCapabilities.PHANTOMJS
# 指定のURLのページを開きます。
# ここでは、絶対パスを渡していますが、 config.base_url にURLを設定していると、
# 相対パスを指定することができます。
browser.open_url(PLOOM_URL)
# s というのが操作したい要素を選択するための関数です。
# Selenide の $ に該当します。
# s に文字列を渡すと CSS セレクタとして扱われます。
# set_value で input に値をセットします。
# 暗黙的にクリアされた後に入力が行われます。
s('input[name="ACCOUNTUID"]').set_value(PLOOM_ACCOUNTUID)
s('input[name="PASSWORD"]').set_value(PLOOM_PASSWORD)
# click() で要素をクリックします。
s('input[type="SUBMIT"]').click()
s('input[type="checkbox"]').click()
s('input[type="submit"]').click()
# セレクタに xpath を利用したい場合には by.xpath を指定します。
s(by.xpath('//label[text()="上記内容を確認しました"]')).click()
s(by.xpath('//label[text()="{0}"]'.format(PLOOM_PREFECTURE))).click()
s(by.xpath('//a[text()="予約を申し込む"]')).click()
s(by.xpath('//label[text()="{0}"]'.format(PLOOM_CITY))).click()
s(by.xpath('//a[text()="店舗選択へ"]')).click()
message = None
if PLOOM_SHOP is None:
# ss で要素のコレクションを取得できます。 Selenide の $$ に該当します。
# s と同様にセレクタを渡します。
shops = ss(by.xpath('//a[contains(@class,"is-gold")]/../../..'))
if shops.size() > 0:
message = u'<{0}> 以下の店舗が予約を受け付けています。 :smile:\n'.format(SLACK_MENTION_ID)
for shop in shops:
# 要素内の要素を検索する場合には取得した要素の s を呼びます。
# 要素の text プロパティからテキストが取得できます。
shop_name = shop.s('h2.shop-name').text
shop_address = shop.s('p.address').text
message += u'店舗名: `{0}`, 住所: `{1}`\n'.format(shop_name, shop_address)
else:
xpath = '//h2[@class="shop-name"][text()="{0}"]/..//p[@class="submit"]/a[contains(@class,"is-gold")]'
# Selenide であれば以下のような書き方ができるのですが、
# 現状 Selene だと is_XXXX 系のメソッドでも TimeoutException が出てしまうため、
# コレクションを利用しています。
# shop = s(by.xpath(xpath.format(PLOOM_SHOP)))
# if shop.is_displayed() is True:
shop = ss(by.xpath(xpath.format(PLOOM_SHOP)))
if shop.size() > 0:
message = '<{0}> '.format(SLACK_MENTION_ID)
message += '`{0}` が予約を受け付けています。 :smile:\n'.format(PLOOM_SHOP)
if message is None:
slack_client.api_call(
SLACK_METHOD,
channel=SLACK_CHANNEL_LOG,
text='検索条件に該当する店舗は予約を受け付けていませんでした。 :cry:'
)
else:
message += PLOOM_URL
slack_client.api_call(
SLACK_METHOD,
channel=SLACK_CHANNEL_FOUND,
text=message
)
slack_client.api_call(
SLACK_METHOD,
channel=SLACK_CHANNEL_LOG,
text='検索条件に該当する店舗が予約を受け付けています。 :smile:'
)
except:
slack_client.api_call(
SLACK_METHOD,
channel=SLACK_CHANNEL_LOG,
text='<{0}> 店舗検索に失敗しました。 :angry:\n```{1}```'
.format(SLACK_MENTION_ID, traceback.format_exc())
)
raise
if __name__ == '__main__':
main()

上記のサンプルでは用いませんでしたが、 アサート用のメソッド should など Selenide と同等のメソッドが色々用意されています。


最後に

開発途上で完全互換という訳ではないですが、 Python でも Selenide ライクなブラウザ操作ができるようになっており、 Selenide を使っていて Python でも同じことやりたかった人だったり、 Python で Selenium のラッパー探していた人にとっては要注意なライブラリだと思います。





  1. 今は購入済みです。月曜0時過ぎに一気に解放されるっぽいです。一度予約すると確認できなくなるので詳細は不明。 



  2. 予約完了までテストできそうになかったので、ここで止めてます。