測試開發筆記六(移動端app自動化測試)

01 | Appium環境安裝


appium生態工具

  • adb:android的控制工具,用于獲取android的各種數據和控制
  • Appium Desktop:內嵌了appium server和inspector的綜合工具
  • Appium Server:appium的核心工具,命令行工具
  • Appium client:各種語言的客戶端封裝庫,用于連接appium server
  • AppCrawler自動遍歷工具

環境安裝

  • java1.8
    1.下載地址:https://www.oracle.com/cn/java/technologies/javase-jdk8-downloads.html
    2.配置環境變量
    JAVA_HOME D:\Android\Java\jdk1.8.0_25
    classpath .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
    path %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
    3.驗證
    java -version
    javac -version
  • android sdk
    1.下載:http://tools.android-studio.org/index.php/sdk,下載壓縮包即可
    2.升級:解壓后,在readme里找到升級命令,并執行tools\android.bat update sdk --no-ui
    3.配置環境變量:
    ANDROID_HOME D:\Programs\android-sdk-windows
    PATH %ANDROID_HOME%\tools;%ANDROID_HOME%\platform-tools
    4.檢查是否安裝成功:adb
  • node js (>=10),npm(>=6)
  • python3
  • appium-desktop(appium server + appium inspector工具)
    1.下載地址:https://github.com/appium/appium-desktop/releases
    2.如果不需要安裝appium inspector,可通過npm直接安裝appium
    npm install -g appium官方(不推薦)
    npm install -g cnpm --registry=https://registry.npm.taobao.org淘寶鏡像(推薦)
    cnpm install -g appium
    3.驗證:appium(不報錯說明安裝成功)
  • Appium python client
    1.安裝:pip install appium-python-client
  • appium-doctor
    1.安裝:cnpm install -g appium-doctor
    2.使用:appium-doctor

02 | Appium用例錄制


android自動化依賴

  • adb工具
  • 模擬器 or 真機
    1.模擬器:網易mumu,genimotion,sdk自帶模擬器
    2.真機需要root權限
  • Appium Desktop:入門學習工具

具體操作

  • 連接mumu模擬器:adb connect 127.0.0.1:7555
  • 查看設備名稱:adb devices
  • 查看appPackage、appActivity:adb logcat | grep -i displayed
  • appium-desktop操作


    image.png

獲取app的信息

  • app信息
    1.獲取當前界面元素:adb shell dumpsys activity top(推薦)(重點)
    2.獲取任務列表:
    adb shell dumpsys activity activities
    adb shell dumpsys window | grep mCurrent 獲取當前頁面信息
  • app入口
    1.adb logcat | grep -i displayed(推薦)(重點)
    2.aapt dump badging mobike.apk | grep launchable-activity
    3.apkanalyzer 最新版本中的sdk才有
  • 啟動應用
    1.adb shell am start -W -n com.xueqiu.android/.view.WelcomeActivityAlias -S(重點)

03 | 元素定位與隱式等待


Capability設置

  • 官方文檔:https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md
  • app apk地址
  • appPackage 包名
  • appActivity Activity名字
  • unicodeKeyBoard resetKeyBoard 是否需要輸入非英文之外的語言并在測試完成后重置輸入法
  • dontStopAppOnReset 首次啟動后不終止app進程(可在調試或運行時提升運行速度)
  • skipDeviceInitialization 跳過安裝、權限設置等操作(可在調試或運行時提升運行速度)
  • autoGrantPermissions:True 默認選擇,如是否獲取通訊錄等
  • noReset:True 不清空應用數據,默認false
  • fullReset:True 清空應用數據并卸載

by_accessibility_id

driver.find_element_by_accessibility_id("content-desc")

三種經典等待方式

  • 強制等待
  • 隱式等待
  • 顯式等待

示例

from time import sleep

from appium import webdriver

desired_caps = {
    "platformName": "Android",
    "deviceName": "127.0.0.1:7555",
    "platformVersion": "6.0",
    "appPackage": "com.xueqiu.android",
    "appActivity": ".view.WelcomeActivityAlias",
    "noReset": "true",
    "dontStopAppOnReset": "true",
    "skipDeviceInitialization": "true"
}

driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
driver.implicitly_wait(10)
driver.find_element_by_id("com.xueqiu.android:id/tv_search").click()
driver.find_element_by_id("com.xueqiu.android:id/search_input_text").send_keys("alibaba")
driver.find_element_by_accessibility_id("content-desc")
driver.back()
driver.back()
sleep(3)
driver.quit()

04 | app控件定位


android基礎知識

  • 布局
    1.android 是通過布局來管理控件的位置關系,布局過程是把控件根據間距大小擺放在正確的位置
    2.LinearLayout(線性布局)(水平和垂直,用的最多)
    3.RelativeLayout(相對布局)(先確定A控件布局的位置,再根據間距確定B控件布局的位置)
    4.RrameLayout(幀布局)(最低層的布局,其他布局在此布局基礎上再進行布局,用的最多)
    5.AbsoluteLayout(絕對布局)(根據絕對坐標布局,很少用,兼容不好)
    6.TableLayout(表格布局)(將頁面的文字、圖片放在表格里布局)
    7.GridLayout(網絡布局)(與表格布局相似)
    8.ConstraintLayout(約束布局)
    9.布局里不僅能放置控件還能嵌套布局


    image.png
  • Android四大組件
    1.activity 與用戶交互的可視化界面,每個activity有布局結構
    2.service 實現程序后臺運行的解決方案,如點擊home鍵后臺運行程序
    3.content provider 內容提供者,提供程序所需要的數據(可通過文件、數據庫、網絡形式提供)
    4.broadcast receier 廣播接收器,監聽外部事件的到來(比如來電、短信、通知消息)

  • 常用控件
    1.TextView(文本控件)EditText(可編輯文本控件)
    2.Button(按鈕)ImageButton(圖片按鈕)loggleButton(開關按鈕)
    3.ImageView(圖片控件)
    4.CheckBox(復選框控件)RadioButton(單選框控件)

ios基礎知識

  • 布局
    1.去掉了布局的概念,直接用變量間的相對關系完成位置的計算
  • 開發環境
    1.系統:MacOS X
    2.開發工具:Xcode
    3.開發語言:ObjectC
    4.安裝文件:.ipa/.app
  • 注意
    1.使用appium測試必須使用蘋果系統

dom結構解讀

  • 元素定位
    1.實際是控件定位
    2.想要一個腳本同事支持android/ios連個系統,就要保證元素屬性(id,aid,xpath)一致
  • dom
    1.document object model文件對象模型
    2.用于html和js的交互,表示界面的控件層級,界面的結構化描述,常見格式是html、xml,核心元素為節點和屬性
    3.xpath:xml路徑語言,用于xml中的節點定位
  • android的層級結構
    1.anddroid應用的層級結構與html不一樣,是一個定制的xml
    2.app source類似于dom,表示app的層級,代表了界面里所有控件樹的結構
    3.每個控件都有它的屬性(resourceid,xpath,aid),沒有css屬性
  • ios與android的區別
    1.名字和屬性的命名不同(android resourceid, ios name; android content-des, ios accessibility-id)

id、aid、xpath定位方法

  • 定位方式
    1.id定位
    dirver.find_element_by_id(resouce-id)
    driver.find_element(MobileBy.ID,"resource-id")
    2.accessibility_id定位
    dirver.find_element_by_accessibility_id
    driver.find_element(MobileBy.ACCESSIBILITY_ID,"content_desc")
  • xpath定位
    driver.find_element_by_xpath()
  • classname定位(不推薦)

uiautomatorviewer定位工具使用

  • 在sdk\tools下

我的uiautomatorviewer識別的屏幕是橫屏的,需要保存到本地,轉成豎屏再導入,挺麻煩的,如果是這樣,不如用inspector了

示例

from time import sleep
from appium import webdriver


class TestDW:
    def setup(self):
        desired_caps = {
            "platformName": "Android",
            "deviceName": "127.0.0.1:7555",
            "platformVersion": "6.0",
            # "appPackage": "com.xueqiu.android",
            # "appActivity": ".view.WelcomeActivityAlias",
            "noReset": "true",
            "dontStopAppOnReset": "true", 
            "skipDeviceInitialization": "true",
            "unicodeKeyBoard": "true",
            "resetKeyBoard": "true"
        }

        self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
        self.driver.implicitly_wait(10)

    def teardown(self):
        self.driver.back()
        # self.driver.quit()

    def test_search(self):
        """
        1.打開雪球app
        2.點擊搜索輸入框
        3.搜索輸入框輸入“阿里巴巴”
        4.搜索結果中選擇“阿里巴巴”并點擊
        5.獲取阿里巴巴的股價,并判斷價格>200
        """
        self.driver.find_element_by_id("com.xueqiu.android:id/tv_search").click()
        self.driver.find_element_by_id("com.xueqiu.android:id/search_input_text").send_keys("阿里巴巴")
        self.driver.find_element_by_xpath("http://*[@resource-id='com.xueqiu.android:id/name' and @text='阿里巴巴']").click()
        current_price = float(self.driver.find_element_by_id("com.xueqiu.android:id/current_price").text)
        assert current_price > 200
        sleep(3)

05 | app控件交互


元素的常用方法

  • element.click()
  • element.send_key("")
  • element.set_value("") 設置元素的值
  • element.clear() 清除
  • element.is_displayed() 是否可見
  • element.is_enabled() 是否可用
  • element.is_selected() 是否被選中
  • get_attribute(name) 獲取屬性值

元素的常用屬性

  • element.text 獲取元素文本
  • element.location 獲取元素坐標:{'y':19,'x':498}
  • element.size 獲取元素尺寸(高和寬):{'width':500,'height':22}

案例

from time import sleep
from appium import webdriver


class TestDW:
    def setup(self):
        desired_caps = {
            "platformName": "Android",
            "deviceName": "127.0.0.1:7555",
            "platformVersion": "6.0",
            # "appPackage": "com.xueqiu.android",
            # "appActivity": ".view.WelcomeActivityAlias",
            "noReset": "true",
            "dontStopAppOnReset": "true", 
            "skipDeviceInitialization": "true",
            "unicodeKeyBoard": "true",
            "resetKeyBoard": "true"
        }

        self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
        self.driver.implicitly_wait(10)

    def teardown(self):
        self.driver.back()
        # self.driver.quit()

    def test_atrr(self):
        """
        1.打開雪球首頁
        2.定位首頁搜索框
        3.判斷搜索框是否可用,并打印搜索框name屬性值
        4.打印搜索框左上角坐標和它的寬高
        5.向搜索框輸入alibaba
        6.判斷【阿里巴巴】是否可見
        7.如果可見,打印“搜索成功”,如果不可見,打印“搜索失敗”
        """
        search = self.driver.find_element_by_id("com.xueqiu.android:id/tv_search")
        search_enabled = search.is_enabled()
        print(search.text)
        print(search.location)
        print(search.size)
        if search_enabled == True:
            search.click()
            self.driver.find_element_by_id("com.xueqiu.android:id/search_input_text").send_keys("alibaba")
            alibaba_element = self.driver.find_element_by_xpath("http://*[@resource-id='com.xueqiu.android:id/name' and @text='阿里巴巴']")
            # alibaba_element.is_displayed()
            element_display = alibaba_element.get_attribute("displayed")
            if element_display == "true":
                print("搜索成功")
            else:
                print("搜素失敗")

06 | 觸屏操作自動化


TouchAction

案例

from time import sleep
from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction


class TestTouchAction:
    def setup(self):
        desired_caps = {
            "platformName": "Android",
            "deviceName": "127.0.0.1:7555",
            "platformVersion": "6.0",
            # "appPackage": "com.xueqiu.android",
            # "appActivity": ".view.WelcomeActivityAlias",
            "noReset": "true",
            "dontStopAppOnReset": "true",  
            "skipDeviceInitialization": "true",
            "unicodeKeyBoard": "true",
            "resetKeyBoard": "true"
        }

        self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
        self.driver.implicitly_wait(10)

    def teardown(self):
        pass
        # self.driver.back()
        # self.driver.quit()

    def test_atrr(self):
        action = TouchAction(self.driver)
        # action.press(x=386.5,y=1076.3).wait(200).move_to(x=386.5,y=519.5).release().perform()
        window_rect = self.driver.get_window_rect()
        width = window_rect['width']
        height = window_rect['height']
        x1 = int(width / 2)
        y_start = int(height * 4 / 5)
        y_end = int(height * 1 / 5)
        action.press(x=x1, y=y_start).wait(200).move_to(x=x1, y=y_end).release().perform()

07 | 高級定位


xpath定位進階

  • 層級定位
    1.父節點定位子節點
    2.子節點定位父節點
    3.子節點定位兄弟節點
    4.爺爺節點定位孫子節點
  • 基本語法
    1.https://www.runoob.com/xpath/xpath-syntax.html
  • 案例
    1.定位股票
    image.png

    //*[@resource-id='com.xueqiu.android:id/title_container']/ android.widget.TextView[2]/..
    2.定位0988股票的價格
    image.png

    //*[@text='09988']/../../..//*[@resource-id='com.xueqiu.android:id/current_price']

uiautomator定位表達式(一般不用)

  • 參考資料
    https://developer.android.com/reference/android/support/test/uiautomator/UiSelector.html
  • 優點
    1.xpath定位速度慢
    2.uiautomator是android的工作引擎,速度快
  • 缺點
    1.表達式書寫復雜,容易寫錯且IDE沒有提示
  • 用法
    driver.find_element_by_android_uiautomator(表達式).click()
    1.通過text文本定位
    new UiSelector().text("text文本")
    2.文本較長,可模糊匹配
    new UiSelector().textContains("包含text文本")
    3.以某個文本開頭匹配
    new UiSelector().textStartsWith("文本開頭")
    4.用正則表達式textMatches匹配
    new UiSelector().textMatches("正則表達式")
    5.通過resourceid定位
    new UiSelector().resourceId("id")
    6.通過classname定位
    new UiSelector().className("className")
    7.通過content-desc定位
    new UiSelector().description("content-desc屬性")
    8.id與text屬性組合定位
    id_text = 'new UiSelector().resourceId("com.baidu.yuedu:id/webbooktitle").text("我的")'
    driver.find_element_by_android_uiautomator(id_text).click()
    9.class與text屬性組合
    class_text = 'new UiSelector().className("android.widget.TextView").text("圖書")'
    driver.find_element_by_android_uiautomator(class_text).click()
    10.父子關系定位
    new UiSelector().resourceId("").childSelector(text(""))
    11.兄弟定位
    new Uiselector().resourceId("").fromParent(text(""))

滑動定位

from time import sleep
from appium import webdriver


class TestDW:
    def setup(self):
        desired_caps = {
            "platformName": "Android",
            "deviceName": "127.0.0.1:7555",
            "platformVersion": "6.0",
            "appPackage": "com.xueqiu.android",
            "appActivity": ".view.WelcomeActivityAlias",
            "noReset": "true",
            # "dontStopAppOnReset": "true", 
            "skipDeviceInitialization": "true",
            "unicodeKeyBoard": "true",
            "resetKeyBoard": "true"
        }

        self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
        self.driver.implicitly_wait(10)

    def teardown(self):
        self.driver.back()
        # self.driver.quit()

    def test_scroll_find(self):
        self.driver.find_element_by_android_uiautomator('new UiSelector().text("關注")').click()
        self.driver.find_element_by_android_uiautomator('new UiScrollable( new UiSelector().'
                                                        'scrollable(true).instance(0)).'
                                                        'scrollIntoView(new UiSelector().text("美股馬甲")'
                                                        '.instance(0))').click()
        sleep(5)

08 | 顯示等待機制


強制等待

  • sleep(sec) 不推薦

隱式等待

  • driver.implicitly_wait(sec)

顯示等待

  • WebDriverWait(self.driver,10).until(expected_conditions.visibility_of_element_located(locator))
  • 一般頁面元素的呈現
    1.首先,title
    2.其次,dom(隱式等待判斷元素是否已出現在dom樹里,但無法判斷其屬性是否展示)
    3.然后,css(可見visibility)
    4.最后,js(可點擊clickable,顯示等待一般判斷元素是否可見、可點擊)
  • html是自上而下加載的
  • js加載會阻塞html的加載,為此有些js使用異步加載的方式

expected_condition

  • presence_of_element_located 只能判斷此元素在dom里,不能判斷元素的屬性(如可點擊、可見的等)已添加

案例

from time import sleep
from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait


class TestDW:
    def setup(self):
        desired_caps = {
            "platformName": "Android",
            "deviceName": "127.0.0.1:7555",
            "platformVersion": "6.0",
            "appPackage": "com.xueqiu.android",
            "appActivity": ".view.WelcomeActivityAlias",
            "noReset": "true",
            # "dontStopAppOnReset": "true",  
            "skipDeviceInitialization": "true",
            "unicodeKeyBoard": "true",
            "resetKeyBoard": "true"
        }

        self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
        self.driver.implicitly_wait(10)

    def teardown(self):
        self.driver.back()
        # self.driver.quit()

    def test_search(self):
        self.driver.find_element_by_id("com.xueqiu.android:id/tv_search").click()
        self.driver.find_element_by_id("com.xueqiu.android:id/search_input_text").send_keys("alibaba")
        self.driver.find_element_by_xpath("http://*[@resource-id='com.xueqiu.android:id/name' and @text='阿里巴巴']").click()

        locator = (MobileBy.XPATH, "http://*[@text='09988']/../../..//*[@resource-id='com.xueqiu.android:id/current_price']")
        # WebDriverWait(self.driver, 10).until(expected_conditions.element_to_be_clickable(locator))
        ele = WebDriverWait(self.driver, 10).until(lambda x: x.find_element(*locator))
        # ele = self.driver.find_element(*locator)
        print(ele.text)
        current_price = float(ele.text)
        expect_price = 170
        assert current_price > expect_price
        sleep(3)

09 | toast控件識別


toast定位

  • appium使用uiautomator底層的機制來分析抓取toast,并把toast放到控件樹里,但本身并不屬于控件
  • automationName: uiautomator2
  • 必須使用xpath查找
    1.//*[@class='android.widget.Toast']
    2.//*[contains(@text,"xxxx")]

示例

driver.page_source
driver.find_element(MobileBy.XPATH,"http://*[@class='android.widget.Toast']").text
driver.find_element(MobileBy.XPATH,"http://*[contains(@text,'Clicked popup')]").text

10 | 屬性獲取與斷言


get atrribute原理分析

search_ele = self.driver.find_element_by_id("com.xueqiu.android:id/tv_search")
print(search_ele.get_attribute("content-desc"))
print(search_ele.get_attribute("enabled"))
print(search_ele.get_attribute("clickable"))

斷言

  • 普通斷言 assert
a = 10
b = 20
assert a > b # 若斷言失敗,其后的斷言將不會執行
assert 'h' in 'this'
  • Hamcrest斷言
    1.github地址:https://github.com/hamcrest/PyHamcrest
    2.Hamcrest是以測試為目的,組合成靈活表達式的匹配器類庫。用于編寫斷言的框架,使用這個框架編寫斷言,提高可讀性及開發測試的效率
    3.Hamcrest提供了大量被稱為“匹配器”的方法。每個匹配器都設計用戶執行特定的比較操作
    4.Hamcrest的可擴展性強,讓你能夠穿件自定義的匹配器
    5.支持多種語言:java,python,ruby,object-c,php,erlang,swift
    6.示例
assert_that(10,equal_to(9),'這是一個提示')
assert_that(13,close_to(10,2))

11 | appium參數化用例


案例

from time import sleep
import pytest
from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy
from hamcrest import *


class TestDW:
    def setup(self):
        desired_caps = {
            "platformName": "Android",
            "deviceName": "127.0.0.1:7555",
            "platformVersion": "6.0",
            "appPackage": "com.xueqiu.android",
            "appActivity": ".view.WelcomeActivityAlias",
            "noReset": True,
            # "dontStopAppOnReset": True,
            "skipDeviceInitialization": True,
            "unicodeKeyBoard": "true",
            "resetKeyBoard": "true"
        }

        self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
        self.driver.implicitly_wait(10)

    def teardown(self):
        self.driver.find_element(MobileBy.ID, 'com.xueqiu.android:id/action_close').click()
        # self.driver.back()
        # self.driver.quit()

    @pytest.mark.parametrize('searchkey,type,expect_price', [
        ('alibaba', 'BABA', 190),
        ('xiaomi', '01810', 10)
    ])
    def test_search(self, searchkey, type, expect_price):
        self.driver.find_element_by_id("com.xueqiu.android:id/tv_search").click()
        self.driver.find_element_by_id("com.xueqiu.android:id/search_input_text").send_keys(searchkey)
        self.driver.find_element_by_xpath(f"http://*[@text='{type}']").click()

        current_price = self.driver.find_element(MobileBy.XPATH,
                                                 f"http://*[@text='{type}']/../../..//*[@resource-id='com.xueqiu.android:id/current_price']").text
        current_price_float = float(current_price)
        # expect_price = 180
        assert_that(current_price_float, close_to(expect_price, expect_price * 0.2))
        sleep(3)

12 | 數據驅動


https://github.com/tim8709/hogwarts_testing/tree/master/test_appium

13 | Android WebView測試

image.png

純網頁應用

  • 手機端
    1.safari,chrome,Browser for android
    2.不能是第三方瀏覽器(uc,QQ)

  • pc端
    1.安裝Chrome瀏覽器,并能登錄https://www.google.com
    2.下載手機瀏覽器對應的driver版本
    國內鏡像地址:https://npm.taobao.org/mirrors/chromedriver/
    appium github:https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/web/chromedriver.md

  • 客戶端代碼
    1.desirecapability
    "browser" = "Browser" 或者 "browser" = "Chrome"
    "chromedriverExecutable" = "指定driver地址"

  • 查看瀏覽器版本信息
    1.adb shell pm list package | grep browser
    2.adb shell pm dump com.android.browser | grep version

  • appium的chromedriver
    1.默認地址:C:\Users\25236\AppData\Local\Programs\Appium\resources\app\node_modules\appium\node_modules\appium-chromedriver\chromedriver\win
    2.adb查不到手機自帶瀏覽器的版本號,appium可以查到
    [圖片上傳失敗...(image-dd2754-1589359890550)]
    3.下載2.24版本的chromedriver

  • chrome://inspect
    1.直接在pc的chrome中輸入chrome://inspect會空白
    2.要么翻墻,要么下載android webview的離線調試工具

  • 代碼

from appium import webdriver
from time import sleep

class TestBrowser():
    def setup(self):
        des_caps = {
            'platformName': 'android',
            'platformVersion': '6.0',
            'browserName': 'Browser',
            'noRest': True,
            'deviceName': '127.0.0.1:7555',
            'skipServerInstallation': 'true',
            'chromedriverExecutableDir': 'xxx', # 路徑下可放多個webdriver
            'chromedriverExecutable': 'D:\workspace\hogwarts\dirver\chromedriver2.24.exe'  # 指定Chromedriver存放的位置
        }
        self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", des_caps)
        self.driver.implicitly_wait(10)

    def teardown(self):
        # self.driver.quit()
        pass
    
    def test_browser(self):
        self.driver.get("http://www.baidu.com")
        sleep(5)

android混合頁面測試

  • 原生應用中嵌入了webview
  • 如何判斷頁面是webview
    1.斷網查看
    2.看加載條
    3.看頂部是否有關閉按鈕
    4.下拉刷新,看頁面是否刷新
    5.下拉刷新,看是否有網絡提供方(下拉騰訊新聞,提示qq瀏覽器內核)
    6.用工具查看(inspect)
  • webview
    1.android系統提供能顯示網頁的系統控件
  • 前提條件
    1.pc
    瀏覽器能訪問 http://ww.google.com
    chromedriver 下載對應的版本
    2.手機端
    應用代碼需要打開webview開關,后讓開發留個后門
    3.代碼
    appPackage,appActivity
    desirecapability添加:chromedriverExecutable:driver路徑
  • 元素定位
    1.頁面渲染后,通過accessibility定位(不穩定)
self.driver.find_element(MobileBy.ACCESSIBILITY_ID,'xxx')

2.切換上下文定位

# 進入webview頁面后
print(self.driver.contexts)
self.driver.switch_to.context(self.driver.contexts[-1])
print(self.driver.page_source)

print(self.driver.window_handles)
self.driver.switch_to.window(self.driver.window_handles[-1])

3.若知道html的地址,可用瀏覽器直接訪問(不穩定)
4.adb logcat -v time | grep http 查找當前頁面地址

  • 遇到的坑
    1.設備
    android模擬器6.0默認支持webview操作(mumu不可以,genimotion和sdk自帶的emulator可以)
    其他模擬器和物理機需要打開app內開關(webview調試開關)
    2.pc瀏覽器定位元素
    chrome瀏覽器- chrome62才能更好的看見webview的內部,其他版本都有bug
    換成chromium瀏覽器可以避免很多坑,展示效果和速度比chrome要快
    3.代碼
    不推薦使用find_element_by_accessibility_id()定位頁面元素
    推薦使用switch_to.context()、switch_to.window()

  • webview開關
    1.文檔:https://developers.google.com/web/tools/chrome-devtools/remote-debuging/webviews?hl=zh-cn
    2.android6.0不打開也能查看頁面結構
    3.啟動webview調試,需在webview類上調用靜態方法,此設置適用于應用的所有WebView

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
        Webview.setWebContentsDebuggingEnabled(true);
}

14 | 實戰1


appium的設計理念

image.png
  • client/server設計模式
  • server可以放在任何地方

連接真機&模擬器

  • 打開開發者模式:連續點擊版本號7次
  • 開發者選項中打開usb調試
  • 模擬器還是沒連接上,用`adb connect
  • 真機還需要安裝驅動,可以用豌豆莢或手機助手

測試蘋果機需要的環境

  • pc:macOS、Xcode、libimobiledevice、ideviceinstaller
  • 手機:WDA facebook(webdriver agent)

模擬器

  • mumu(推薦)
  • Genimotion
    1.可以模擬不同手機尺寸,不同版本的手機設備
  • emulator
    1.android sdk自帶的emulator,使用Android Studio創建
    2.emulator啟動方法
    emulator -list-avds
    emulator @Pixel_3a_XL_API_23_x86-6-0

模擬器安裝卸載應用

  • 下載apk到電腦
  • 方式1:拖動apk到模擬器安裝,長按應用拖拽刪除
  • 方式2:
    adb install xx.apk
    adb install -r xx.apk(覆蓋安裝)
    adb -s 指定設備
    adb uninstall 包名

adb常用命令

  • adb devices
  • adb logcat | grep -i displayed
  • adb shell am start -n activity

15 | 實戰2


常用的接口信息

  • 4723:客戶端與server端通信的端口
  • 5037:adb server與adbd通信端口
  • 7555:windows上模擬器與pc機建立連接的端口
  • 800*:與chromdriver通信的端口號

adb

image.png

元素定位

  • 推薦使用優先級
    1.ID定位(優先級最高)
    2.Accessibility ID定位(其次)
    3.XPATH定位(速度慢,但定位靈活)
    4.Uiautomator定位(速度快,但語法復雜)

企業微信

from time import sleep
import pytest
from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy


class TestBrowser():

    def setup_class(self):
        des_caps = {
            'platformName': 'android',
            'deviceName': '127.0.0.1:7555',
            'appPackage': 'com.tencent.wework',
            'appActivity': '.launch.WwMainActivity',
            'noReset': True,
            'chromedriverExecutable': 'D:\workspace\hogwarts\dirver\chromedriver2.24.exe'  # 指定Chromedriver存放的位置
        }
        self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", des_caps)
        self.driver.implicitly_wait(10)

    def teardown_class(self):
        self.driver.quit()

    # def setup(self):
    #     pass
    #
    # def teardown(self):
    #     self.driver.back()

    # @pytest.fixture()
    # def add_fixture(self):
    #     yield
    #     self.driver.back()

    @pytest.mark.parametrize('name,gender,phone', [
        ('姓名16', '男', '13200000016'),
        ('姓名17', '男', '13200000017')
    ])
    def test_add_member(self, add_fixture, name, gender, phone):
        # name = "姓名6"
        # gender = "男"
        # phone = "13211111116"
        # 進入通訊錄
        self.driver.find_element(MobileBy.XPATH, '//*[@text="通訊錄"]').click()
        # 滾動查找“添加成員”
        self.driver.find_element_by_android_uiautomator('new UiScrollable( new UiSelector().'
                                                        'scrollable(true).instance(0)).'
                                                        'scrollIntoView(new UiSelector().text("添加成員")'
                                                        '.instance(0))').click()
        # 手動添加
        self.driver.find_element(MobileBy.ID, "com.tencent.wework:id/c7g").click()

        current_act = self.driver.current_activity
        assert ".contact.controller.ContactAddActivity" in current_act

        self.driver.find_element(MobileBy.XPATH, '//*[contains(@text,"姓名")]/../*[@text="必填"]').send_keys(name)
        self.driver.find_element(MobileBy.ID, 'com.tencent.wework:id/dux').click()
        sleep(1)
        if gender == "男":
            self.driver.find_element(MobileBy.XPATH, '//*[@text="男"]').click()
        else:
            self.driver.find_element(MobileBy.XPATH, '//*[@text="女"]').click()

        self.driver.find_element(MobileBy.ID, 'com.tencent.wework:id/eq7').send_keys(phone)
        self.driver.find_element(MobileBy.ID, 'com.tencent.wework:id/gur').click()
        sleep(1)
        # print(self.driver.page_source)
        assert "添加成功" in self.driver.find_element(MobileBy.XPATH, '//*[@text="添加成功"]').text

    @pytest.mark.parametrize('name', [
        '姓名5', '姓名7'
    ])
    def test_delete_member(self, name):
        self.driver.find_element(MobileBy.XPATH, '//*[@text="通訊錄"]').click()
        self.driver.find_element(MobileBy.XPATH, f'//*[@text="{name}"]').click()
        self.driver.find_element(MobileBy.ID, 'com.tencent.wework:id/guk').click()
        self.driver.find_element(MobileBy.ID, 'com.tencent.wework:id/azd').click()
        self.driver.find_element(MobileBy.ID, 'com.tencent.wework:id/duq').click()
        self.driver.find_element(MobileBy.ID, 'com.tencent.wework:id/b_4').click()
        sleep(3)
        deleted_member = self.driver.find_elements(MobileBy.XPATH, f'//*[@text="{name}"]')
        assert len(deleted_member) == 0

    def test_delete_member2(self):
        self.driver.find_element(MobileBy.XPATH, '//*[@text="通訊錄"]').click()
        while True:
            members = self.driver.find_elements(MobileBy.XPATH, '//*[contains(@text,"姓名")]')
            if len(members) ==0:
                print("沒有要刪除的成員了")
                break
            text = members[0].text
            members[0].click()
            self.driver.find_element(MobileBy.ID, 'com.tencent.wework:id/guk').click()
            self.driver.find_element(MobileBy.ID, 'com.tencent.wework:id/azd').click()
            self.driver.find_element(MobileBy.ID, 'com.tencent.wework:id/duq').click()
            self.driver.find_element(MobileBy.ID, 'com.tencent.wework:id/b_4').click()
            sleep(3)
            assert text not in self.driver.page_source

16 | 實戰3(PO設計模式在Appium中的應用)


PageObject設計原則

  • 用公共方法代表UI提供的服務
  • 方法應該返回其他的PageObject或返回用于斷言的數據
  • 同樣的行為不同的結果可以建模為不同的方法
  • 不要在方法內加斷言
  • 不要暴露頁面內部的元素給外部
  • 不需要為UI內所有元素建模

代碼

https://github.com/tim8709/hogwarts_testing/tree/master/test_appium/page_object2

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。