#回帰テスト
Appiumを使ってAndroidアプリ回帰テスト用アプリを作成しました
インストール方法に関しては解説してくれているサイトが山ほどあるので割愛します
appium -v 1.8.1
Rubyクライアントです
appiumの公式ドキュメント
http://appium.io/docs/en/writing-running-appium/caps/
Rubyクライアントであるappium_libの公式ドキュメント
https://www.rubydoc.info/github/appium/ruby_lib/index
##アプリをエミュレータ(or実機)にインストール
require "rubygems"
require "appium_lib"
require "date"
include Appium
class Hogepium
# summary イニシャライザ
def initialize
@wait_time = 30
@package = "androidmanifest.xmlのpackageと同じもの(例:jp.co.fuga.hogeapp)"
@desired_caps = {
caps: {
appPackage: @package,
appActivity: "androidmanifest.xmlに設定してある一番最初に起動するactivityをパッケージつきで設定する(例:jp.co.fuga.hogeapp.activity.FirstActivity)",
platformName: "Android",
app: "ビルドしたapkファイルをフルパスで設定する",
automationName: "Appium",
deviceName: "Android Emulator",
# 日本語入力に必要な設定
unicodeKeyboard: 'true',
# avdマネージャで自分が作成したavd名のうちスペースをアンダーバーで置き換え設定する
avd: "Pixel_XL_API_27"
},
appium_lib: {
# この設定値はよくわからない
wait: 0
}
}
end
# ドライバをスタートする(アプリをエミュレータにインストールする)
def start_driver
# この2つめのパラメータもよくわからない
@driver = Driver.new(@desired_caps, false).start_driver
end
end
require_relative "./hogepium.rb"
h = Hogepium.new
h.start_driver
$ruby hogescenario.rb
で指定したエミュにアプリがインストールアンド起動されます
**先にAndroid Studioで対象のエミュを起動させておくこと。**Appiumはエミュの起動までやってくれないので
別記事で見かけました。設定項目のうち
deviceName: "avd managerのname",
platformVersion: "対象エミュレータにインストールしてあるOSのバージョン(例:7.1.1)"
ではなく
deviceName: "Android Emulator",
avd: "avdマネージャで自分が作成したavd名のうちスペースをアンダーバーで置き換えたもの"を設定することでエミュレータもappiumが起動してくれました
また、パッケージをインスタンス変数にした理由は後述します
色々なセレクタを追加していく前に便利メソッドをhogepiumに追加しておきます
# 現在のページをDOM形式で取得できるプロパティ。シナリオ途中で失敗したときとかにコンソールに出力させると、指定したidやxpathなんかが間違っていないか確認できる
def page_source
@driver.page_source
end
# 現在のactivityが取得できるプロパティ。うまくつかえば画面遷移したかどうかをハンドリングできるかと思ったけんどFragment使っていたらダメですね
def current_activity
@driver.current_activity
end
# エミュレータで(実機でも可)表示している画面のスクショ撮って保存できる
def screen_shot
@driver.save_screenshot("/任意のパス/#{Time.now}.png")
end
##色々なセレクタを実装していく
private
# Viewに設定されているIDを基に要素を取得
def selector_from_id(id)
@driver.find_elements(:id, id).first
end
# Viewに設定されているIDを基に要素が見つかるまでスクロールして取得する
# Androidはメモリを節約するために画面範囲外の部分は何も描画していない。
# そのためfind_elements(:id)だけでは目的の要素を取得することができないためこのセレクタは超大事
# このセレクタを何度か使い画面中程から上にある要素を探したい場合でも、「一番下までスクロール -> 見つからなかったら上方向にスクロール」を自動でやってくれる。
# 先ほどのパッケージはここで使う
def selector_from_uiselector_from_id(id)
@driver.find_elements(:uiautomator, "new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().resourceId(\"#{@package}:id/#{id}\"));").first
end
# TextViewに設定されているテキストを基に要素を取得する
# ListViewの目的行を取得したりする際に使う
def selector_from_textview_in_text(text)
@driver.find_elements(:xpath, "//android.widget.TextView[@text=\"#{text}\"]").first
end
# Buttonに設定されているテキストを基に要素を取得する
# IDが設定されていないButtonを特定する際に使う
def selector_from_Button_in_text(text)
@driver.find_elements(:xpath, "//android.widget.Button[@text=\"#{text}\"]").first
end
# 画面に設置されているEditTextを上から数えて指定したposition番目のものを取得する
def selector_from_view_edittext(position)
@driver.find_elements(:xpath, "//android.widget.EditText[#{position}]").first
end
# selector_from_view_edittextのTextViewバージョン
def selector_from_view_textview(position)
@driver.find_elements(:xpath, "//android.widget.TextView[#{position}]").first
end
# 同上
def selector_from_view_button(position)
@driver.find_elements(:xpath, "//android.widget.Button[#{position}]").first
end
##色々なイベントを実装していく
private
# to_rightと書いていますがstart_nとoffset_nの値変えればどの方向でもスワイプできます
def swipe_to_right(element)
start_x = element.rect.x + element.rect.width / 10
start_y = element.rect.y + element.rect.height / 2
offset_x = element.rect.width - element.rect.width / 10 - start_x
offset_y = 0
TouchAction.new(@driver).swipe(start_x: start_x, start_y: start_y, offset_x: offset_x, offset_y: offset_y, duration: 1000).perform
end
# EditTextにstrを入力する
def input(element, str)
element.send_keys(str)
end
def click(element)
element.click
end
##エンジンを実装する
private
def get_element(wait_time, &get_element)
start_time = Time.now
while Time.now - start_time < wait_time
begin
element = yield
if !element.nil? && element.enabled? && element.displayed?
break
end
rescue => e
# p e.to_s
# p e.message.to_s
end
end
if element
element
else
p "対象Element見つからず!!!"
# p page_source
@driver.quit
exit
end
end
##ロガー
class Logger
def self.debug(classobj, method, *args)
o = "【" << classobj.class.name << "#"<< method.to_s << "】:"
if args
o << args.join(", ")
end
p o
end
end
##シナリオ作成に必要なセレクタとイベントを組み合わせたものを色々と実装する
# summary IDを基にElementを検索しクリックする
# param id Viewに設定されているID
# param wait_time 対象要素を発見するまでの待ち時間 default 30
def click_from_id(id, wait_time = @wait_time)
Logger::debug(self, __method__, id)
click(get_element(wait_time) do
selector_from_id(id)
end)
screen_shot
end
# summary 初期表示時点では画面に表示されていないViewをスクロールして探しクリックする
# param id Viewに設定されているID
# param wait_time 対象要素を発見するまでの待ち時間 default 30
def click_from_id_scroll(id, wait_time = @wait_time)
Logger::debug(self, __method__, id)
click(get_element(wait_time) do
selector_from_uiselector_from_id(id)
end)
screen_shot
end
# summary TextViewに設定されている文字列を基に検索しクリックする
# param text 検索対象文字列
# param wait_time 対象要素を発見するまでの待ち時間 default 30
def click_from_textview_in_text(text, wait_time = @wait_time)
Logger::debug(self, __method__, text)
click(get_element(wait_time) do
selector_from_textview_in_text(text)
end)
screen_shot
end
# summary Buttonに設定されている文字列を基に検索しクリックする
# param text 検索対象文字列
# param wait_time 対象要素を発見するまでの待ち時間 default 30
def click_from_button_in_text(text, wait_time = @wait_time)
Logger::debug(self, __method__, text)
click(get_element(wait_time) do
selector_from_Button_in_text(text)
end)
screen_shot
end
# summary 上から数えてposition番目のEditTextに対して文字を入力する
# param position 対象画面内の上から数えて何番目のEditTextViewか 1から開始
# param num 入力文字列
# param wait_time 対象要素を発見するまでの待ち時間 default 30
def input_from_view_to_edittext(position, str, wait_time = @wait_time)
Logger::debug(self, __method__, position, str)
input(get_element(wait_time) do
selector_from_view_edittext(position)
end, str)
screen_shot
end
# summary IDを基に検索したEditTextに対して文字を入力する
# param position 対象画面内の上から数えて何番目のEditTextViewか 1から開始
# param num 入力文字列
# param wait_time 対象要素を発見するまでの待ち時間 default 30
def input_from_id_to_edittext(id, str, wait_time = @wait_time)
Logger::debug(self, __method__, id, str)
input(get_element(wait_time) do
selector_from_id(id)
end, str)
end
# summary Buttonをクリックする
# param position 対象画面内の上から数えて何番目のButtonか 1から開始
# param wait_time 対象要素を発見するまでの待ち時間 default 30
def click_from_view_to_button(position, wait_time = @wait_time)
Logger::debug(self, __method__, position)
click(get_element(wait_time) do
selector_from_view_button(position)
end)
screen_shot
end
# summary 対象Elementを左から右方向へスワイプする Elementはidで検索する
# param id Viewに設定されているid
# param wait_time 対象要素を発見するまでの待ち時間 default 30
def swipe_to_right_from_id(id, wait_time = @wait_time)
Logger::debug(self, __method__, id)
swipe_to_right(get_element(wait_time) do
selector_from_id(id)
end)
screen_shot
end
# summary 指定された座標をタップする。
# param x x座標 default 中央
# param y y座標 default 中央
def click_from_x_y(x = @driver.window_size.width / 2, y = @driver.window_size.height / 2)
Logger::debug(self, __method__, x, y)
TouchAction.new(@driver).tap(x: x, y: y).perform
screen_shot
end
##シナリオ作成
require_relative "./hogepium.rb"
h = Hogepium.new
h.start_driver
# ほげアイコンをタップ
h.click_from_id("hoge_id")
# 検索フォームに検索ワードを入力
h.input_from_id_to_edittext("search", "検索ワード")
# 検索ボタン押下
h.click_from_id("search_button")
# 検索結果ListViewから任意のTextViewをタップ
h.click_from_textview_in_text("目的行")
# 画面遷移後のページの中程にあるアイコンをクリック
h.click_from_id_scroll("confirm_icon")
##メモ
・automationName: "Appium"にすると非常に動きが遅くなるので@wait_timeは30ぐらいがちょうどいいと思う
・ドライバは明示的にquitしなくても勝手にquitされる
・色々やっているうちにautomationNameにuiautomator2を指定するとエラーが発生し動作しなくなりましたが対処方法として下記2コマンドを叩くと良いようです
adb uninstall io.appium.uiautomator2.server
adb uninstall io.appium.uiautomator2.server.test
おわり