移動(dòng)端UI自動(dòng)化測(cè)試--Appium和Cucumber的完美結(jié)合

大綱

├── 簡(jiǎn)介
├── 目的
├── UI自動(dòng)化測(cè)試框架的選擇
├── 環(huán)境配置
├── 案例
├── 借助Appium來(lái)進(jìn)行元素定位
└── 源碼地址

1.簡(jiǎn)介

在日常開(kāi)發(fā)中,自動(dòng)化測(cè)試往往是開(kāi)發(fā)人員比較頭痛的事,特別是UI的自動(dòng)化測(cè)試更是投入大收益小,很多公司情愿多招一個(gè)測(cè)試人員,也不愿意自己搭建一套UI自動(dòng)化測(cè)試系統(tǒng)。

前幾年使用TDD模式和XCode自帶的XCTest開(kāi)發(fā)過(guò)“Lighten”的早期版本,但后來(lái)由于各種原因,測(cè)試用例“年久失修”基本已經(jīng)報(bào)廢,現(xiàn)在基本全靠人工測(cè)試。在使用TDD模式開(kāi)發(fā)的時(shí)候,優(yōu)點(diǎn)挺多,比如能增強(qiáng)自己的全局思維,跳出牛角尖,從使用者的角度去設(shè)計(jì)接口,減少了很多冗余代碼。當(dāng)然缺點(diǎn)也明顯,比如開(kāi)發(fā)人員要把大量時(shí)間用在編寫(xiě)測(cè)試用例上,而且隨著版本的迭代更新,測(cè)試用例也要跟著更新,大大的增加了開(kāi)發(fā)人員的工作量。

這里不詳細(xì)討論單元測(cè)試和邏輯測(cè)試,主要探討一下UI自動(dòng)化測(cè)試的學(xué)習(xí)和實(shí)踐。
項(xiàng)目源碼
腳本源碼

2.目的

  • 在APP交到測(cè)試或產(chǎn)品手里的時(shí)候,保證最起碼頁(yè)面顯示和跳轉(zhuǎn)邏輯等功能是正確的;
  • 減少后期的開(kāi)發(fā)迭代過(guò)程中,基本功能的自測(cè)時(shí)間;

3.UI自動(dòng)化測(cè)試框架的選擇

基本要求

  • 支持不同平臺(tái)的一套框架,包括安卓、蘋(píng)果和前端等;
  • 集成自動(dòng)化框架,對(duì)原有項(xiàng)目的侵入盡量要小,接入成本盡量低;
  • 穩(wěn)定性要好;
  • 可擴(kuò)展性好;

市場(chǎng)上有很多自動(dòng)化的框架,比如:Instrumentation、UIAutomator、Appium、UIAutomation、Calabash-ios等待,那我們應(yīng)該怎樣去選擇呢?

大廠(chǎng)已經(jīng)為我們開(kāi)好路了,我們直接上車(chē)即可。

根據(jù)市場(chǎng)調(diào)查,最終我們選擇的UI自動(dòng)化測(cè)試框架是:Appium + Cucumber 的模式,其基本滿(mǎn)足我先前提的所有要求。

那么什么是Appium呢?

原文是英文的,我這里做下總結(jié)。

說(shuō)白了,Appium就是一個(gè)適用于native、hybird、mobile web和desktop apps等開(kāi)發(fā)模式并支持模擬器(iOS、Android)和真機(jī)(iOS、Android、Windows、Mac)測(cè)試的、開(kāi)源的跨平臺(tái)自動(dòng)化測(cè)試工具。Appium支持iOS、Android、Windows等多個(gè)平臺(tái)的應(yīng)用程序自動(dòng)化測(cè)試,而且每個(gè)平臺(tái)都有一個(gè)或多個(gè)驅(qū)動(dòng)程序支持,我們可以根據(jù)不同的平臺(tái)安裝和配置驅(qū)動(dòng)程序,具體的看上面文檔。

Appium的優(yōu)點(diǎn)

  • 1、所有平臺(tái)都使用標(biāo)準(zhǔn)化的APIs,你無(wú)需重新編譯和修改你的應(yīng)用;
  • 2、你可以使用任何你喜歡的與WebDriver兼容的語(yǔ)言(如:Java、Objective-C、JavaScript、PHP、Python、Ruby、C#、Clojure、Perl),結(jié)合Selenium WebDriver API和指定語(yǔ)言的客戶(hù)端框架編寫(xiě)測(cè)試用例;
  • 3、你可以使用任何測(cè)試框架;
  • 4、Appium已經(jīng)內(nèi)建moblie web和hybird app支持。在同一個(gè)腳本中,你能在原生自動(dòng)化和webView自動(dòng)化中無(wú)縫切換,因?yàn)樗麄兌际褂昧藰?biāo)準(zhǔn)的WebDriver模型,這已經(jīng)成為web自動(dòng)化測(cè)試的標(biāo)準(zhǔn);

Appium客戶(hù)端下載

Cucumber

按照慣例,這里做下總結(jié):

Cucumber是一個(gè)能夠理解用普通語(yǔ)言來(lái)描述測(cè)試用例,支持行為驅(qū)動(dòng)開(kāi)發(fā)(BDD)的自動(dòng)化測(cè)試工具,使用用Ruby編寫(xiě),也支持Java和·Net等多種開(kāi)發(fā)語(yǔ)言。

什么叫做用普通語(yǔ)言來(lái)描述測(cè)試用例呢,看下具體的案例,我的“引導(dǎo)頁(yè)”的測(cè)試用例:

@guidepage
Feature: 引導(dǎo)頁(yè)
  1.首次安裝應(yīng)用,判斷是否展示引導(dǎo)頁(yè);
    滑到最后一張,判斷是否展示“登錄/注冊(cè)”和“進(jìn)入首頁(yè)”兩個(gè)按鈕;
    點(diǎn)擊“登錄/注冊(cè)”按鈕,判斷是否展示登錄界面。
  2.滑動(dòng)到最后一張引導(dǎo)頁(yè),點(diǎn)擊“進(jìn)入首頁(yè)”按鈕,判斷引導(dǎo)頁(yè)是否還存在。

  @guide_01
  Scenario: 首次安裝應(yīng)用,展示引導(dǎo)頁(yè);滑動(dòng)到最后一張引導(dǎo)頁(yè),展示“登錄/注冊(cè)”和“進(jìn)入首頁(yè)”兩個(gè)按鈕
    When 展示引導(dǎo)頁(yè)
    Then 滑動(dòng)到最后一頁(yè)
    Then 展示“登錄/注冊(cè)”和“進(jìn)入首頁(yè)”兩個(gè)按鈕
    When 點(diǎn)擊“登錄/注冊(cè)”按鈕
    Then 展示登錄界面

  @guide_02
  Scenario: 點(diǎn)擊最后一張引導(dǎo)頁(yè)“進(jìn)入首頁(yè)”按鈕,判斷引導(dǎo)頁(yè)是否還存在
    When 滑動(dòng)到最后一張引導(dǎo)頁(yè),點(diǎn)擊“進(jìn)入首頁(yè)”按鈕
    Then 退出引導(dǎo)頁(yè)

也許你現(xiàn)在不明白每一行,每一個(gè)關(guān)鍵字的含義,沒(méi)關(guān)系,這個(gè)文檔上都有。

當(dāng)然也支持全中文版的,但是感覺(jué)區(qū)分沒(méi)那么明顯,可以通過(guò)cucumber --i18n-languages語(yǔ)句查看支持的語(yǔ)言(前提是已經(jīng)配置好環(huán)境),比如中文的,在終端執(zhí)行cucumber --i18n-keywords zh-CN:

| feature | "功能"  |
| background  | "背景"  |
| scenario  | "場(chǎng)景", "劇本"  |
| scenario_outline | "場(chǎng)景大綱", "劇本大綱"  |
| examples  | "例子"  |
| given | "* ", "假如", "假設(shè)", "假定" |
| when  | "* ", "當(dāng)" |
| then  | "* ", "那么"  |
| and | "* ", "而且", "并且", "同時(shí)" |
| but | "* ", "但是"  |
| given (code)  | "假如", "假設(shè)", "假定"  |
| when (code) | "當(dāng)" |
| then (code) | "那么"  |
| and (code)  | "而且", "并且", "同時(shí)"  |
| but (code)  | "但是"  |

4.環(huán)境配置

Cucumber

Cucumber的安裝和案例請(qǐng)參考文檔,非常詳細(xì)

Appium環(huán)境配置

Appium文檔

第三方博客iOS版

我這里使用的Ruby語(yǔ)言編寫(xiě),所以你可能需要了解下Ruby的基本語(yǔ)法。

環(huán)境弄好了,趕緊搞個(gè)案例爽一下。

5.案例

(1)、新建文件夾存放項(xiàng)目(AutoTestDemo)

cd Desktop
mkdir AutoTestDemo

進(jìn)入 AutoTestDemo 目錄

(2)、初始化cucumber

cucumber --init

執(zhí)行上面命令,會(huì)生成如下目錄結(jié)構(gòu):

features # 存放feature的目錄
├── step_definitions # 存放steps的目錄
└── support # 環(huán)境配置
    └── env.rb

(3)、創(chuàng)建Gemfile文件

創(chuàng)建Gemfile文件

touch Gemfile

打開(kāi)Gemfile,導(dǎo)入Ruby庫(kù)

source 'https://www.rubygems.org'
 
gem 'appium_lib',         '~> 9.7.4'
gem 'rest-client',        '~> 2.0.2'
gem 'rspec',              '~> 3.6.0'
gem 'cucumber',           '~> 2.4.0'
gem 'rspec-expectations', '~> 3.6.0'
gem 'spec',               '~> 5.3.4'
gem 'sauce_whisk',        '~> 0.0.13'
gem 'test-unit',          '~> 2.5.5' # required for bundle exec ruby xunit_android.rb

(4)、安裝ruby依賴(lài)庫(kù)

# 需要先安裝bundle
gem install bundle

# 安裝ruby依賴(lài)
bundle install

(5)、新建apps目錄

apps目錄用于存放,被測(cè)試的app包

mkdir apps

運(yùn)行目標(biāo)項(xiàng)目,在Products文件夾中找到.app結(jié)尾的包,放到apps目錄下,等待測(cè)試。

打包app包

(6)、配置運(yùn)行基本信息

  • 1.進(jìn)入features/support目錄,新建appium.txt文件
  • 2.編輯appium.txt文件,這里只配置了iOS的模擬器和真正代碼
[caps]
# 模擬器
platformName = "ios"
deviceName = "iPhone X"
platformVersion = "11.2"
app = "./apps/AutoUITestDemo.app"
automationName = "XCUITest"
#noReset="true"

# 真機(jī)
# platformName = "ios"
# deviceName = "xxx"
# platformVersion = "10.3.3"
# app = "./apps/AutoUITestDemo.app"
# automationName = "XCUITest"
# udid = "xxxx"
# xcodeOrgId = "QT6N53BFV6"
# xcodeSigningId = "ZHH59G3WE3"
# autoAcceptAlerts = "true"  
# waitForAppScript = "$.delay(5000); $.acceptAlert();" # 處理系統(tǒng)彈窗

[appium_lib]
sauce_username = false
sauce_access_key = false

使用xcrun simctl list devices語(yǔ)句查看系統(tǒng)支持的模擬器版本

查看系統(tǒng)支持的模擬器版本
    1. 打開(kāi)env.rb文件,配置啟動(dòng)入口
# This file provides setup and common functionality across all features.  It's
# included first before every test run, and the methods provided here can be
# used in any of the step definitions used in a test.  This is a great place to
# put shared data like the location of your app, the capabilities you want to
# test with, and the setup of selenium.

require 'rspec/expectations'
require 'appium_lib'
require 'cucumber/ast'

# Create a custom World class so we don't pollute `Object` with Appium methods
class AppiumWorld
end

caps = Appium.load_appium_txt file: File.expand_path('../appium.txt', __FILE__), verbose: true
# end
Appium::Driver.new(caps, true)
Appium.promote_appium_methods AppiumWorld

World do
  AppiumWorld.new
end

Before { $driver.start_driver }
After { $driver.driver_quit }

(7)、在features目錄下,新建guide.feature文件,用來(lái)描述測(cè)試用例

@guidepage
Feature: 引導(dǎo)頁(yè)
  1.首次安裝應(yīng)用,判斷是否展示引導(dǎo)頁(yè);
    滑到最后一張,判斷是否展示“登錄/注冊(cè)”和“進(jìn)入首頁(yè)”兩個(gè)按鈕;
    點(diǎn)擊“登錄/注冊(cè)”按鈕,判斷是否展示登錄界面。
  2.滑動(dòng)到最后一張引導(dǎo)頁(yè),點(diǎn)擊“進(jìn)入首頁(yè)”按鈕,判斷引導(dǎo)頁(yè)是否還存在。

  @guide_01
  Scenario: 首次安裝應(yīng)用,展示引導(dǎo)頁(yè);滑動(dòng)到最后一張引導(dǎo)頁(yè),展示“登錄/注冊(cè)”和“進(jìn)入首頁(yè)”兩個(gè)按鈕
    When 展示引導(dǎo)頁(yè)
    Then 滑動(dòng)到最后一頁(yè)
    Then 展示“登錄/注冊(cè)”和“進(jìn)入首頁(yè)”兩個(gè)按鈕
    When 點(diǎn)擊“登錄/注冊(cè)”按鈕
    Then 展示登錄界面

  @guide_02
  Scenario: 點(diǎn)擊最后一張引導(dǎo)頁(yè)“進(jìn)入首頁(yè)”按鈕,判斷引導(dǎo)頁(yè)是否還存在
    When 滑動(dòng)到最后一張引導(dǎo)頁(yè),點(diǎn)擊“進(jìn)入首頁(yè)”按鈕
    Then 退出引導(dǎo)頁(yè)

我這里寫(xiě)了兩個(gè)測(cè)試場(chǎng)景,分別測(cè)試彈出登錄界面和進(jìn)入首頁(yè)。測(cè)試用例寫(xiě)好后,我們就開(kāi)始編寫(xiě)腳本代碼了,好激動(dòng)。

(8)、在step_definitions目錄下,新建guide.rb文件,用來(lái)存放腳本代碼

  • 在編寫(xiě)rb腳本之前,這里有個(gè)小技巧,就是先用cucumber語(yǔ)法運(yùn)行一下項(xiàng)目,當(dāng)然先保證Appium服務(wù)器是啟動(dòng)狀態(tài)。
  • 在終端進(jìn)入項(xiàng)目下,執(zhí)行cucumber命令。
啟動(dòng)服務(wù)器
運(yùn)行項(xiàng)目
  • 然后把終端中提示我們要實(shí)現(xiàn)的部分拷貝下來(lái),放到rb文件中即可。

  • 最后我們只要在里面去實(shí)現(xiàn)我們的業(yè)務(wù)邏輯就行啦,具體的實(shí)現(xiàn)代碼如下:

# author: BruceLi

=begin
  1.首次安裝應(yīng)用,判斷是否展示引導(dǎo)頁(yè);
    滑到最后一張,判斷是否展示“登錄/注冊(cè)”和“進(jìn)入首頁(yè)”兩個(gè)按鈕;
    點(diǎn)擊“登錄/注冊(cè)”按鈕,判斷是否展示登錄界面。
  2.滑動(dòng)到最后一張引導(dǎo)頁(yè),點(diǎn)擊“進(jìn)入首頁(yè)”按鈕,判斷引導(dǎo)頁(yè)是否還存在。
=end


# 滾動(dòng)引導(dǎo)頁(yè)到最后一頁(yè)
def swipe_to_last_guide_view
    guideIsExist = exists { id("Guide_Page_View") }
    if guideIsExist
      for i in 0...2
        swipe(direction: "left", element: nil)
        sleep(0.25)
      end
    end
  end
  
  # 跳過(guò)引導(dǎo)頁(yè)
  def dismiss_guide_page
    guideExist = exists { id("Guide_Page_View") }
    puts guideExist ? "存在引導(dǎo)頁(yè)面" : "不存在引導(dǎo)頁(yè)面" 
    if guideExist
      swipe_to_last_guide_view
      sleep(1)
      button("Guide_Start_Btn").click
      sleep(0.25)
    end
  end


# @guide_01
#   首次安裝應(yīng)用,判斷是否展示引導(dǎo)頁(yè); 
#   滑到最后一張,判斷是否展示“登錄/注冊(cè)”和“進(jìn)入首頁(yè)”兩個(gè)按鈕; 
#   點(diǎn)擊“登錄/注冊(cè)”按鈕,判斷是否展示登錄界面。
When(/^展示引導(dǎo)頁(yè)$/) do
    guideIsExist = exists { id("Guide_Page_View") } 
    puts guideIsExist ? "存在引導(dǎo)頁(yè)面" : "不存在引導(dǎo)頁(yè)面" 
    expect(guideIsExist).to be true 
end

Then(/^滑動(dòng)到最后一頁(yè)$/) do
    swipe_to_last_guide_view
    sleep(1)
end

Then(/^展示“登錄\/注冊(cè)”和“進(jìn)入首頁(yè)”兩個(gè)按鈕$/) do
    $loginBtnIsExist = exists { id("Guide_Login_Btn") }
    puts $loginBtnIsExist ? "存在“登錄/注冊(cè)”按鈕" : "不存在“登錄/注冊(cè)”按鈕" 
    expect($loginBtnIsExist).to be true

    startBtnIsExist = exists { id("Guide_Start_Btn") }
    puts startBtnIsExist ? "存在“進(jìn)入首頁(yè)”按鈕" : "不存在“進(jìn)入首頁(yè)”按鈕" 
    expect(startBtnIsExist).to be true
end

When(/^點(diǎn)擊“登錄\/注冊(cè)”按鈕$/) do
    if $loginBtnIsExist
        button("Guide_Login_Btn").click
        
    else 
        puts "已登錄"
    end
    sleep(1)
end

Then(/^展示登錄界面$/) do
    if $loginBtnIsExist
        loginViewIsExist = exists { id("login_page") }
        puts loginViewIsExist ? "成功展示“登錄界面" : "展示“登錄界面”失敗" 
        expect(loginViewIsExist).to be true
        sleep(1)
    end
end


# @guide_02 
#   滑動(dòng)到最后一張引導(dǎo)頁(yè),點(diǎn)擊“進(jìn)入首頁(yè)”按鈕,判斷引導(dǎo)頁(yè)是否還存在。
When(/^滑動(dòng)到最后一張引導(dǎo)頁(yè),點(diǎn)擊“進(jìn)入首頁(yè)”按鈕$/) do
    dismiss_guide_page
end

Then(/^退出引導(dǎo)頁(yè)$/) do
    guideIsExist = exists { id("Guide_Page_View") } 
    puts guideIsExist ? "引導(dǎo)頁(yè)面退出失敗" : "成功退出“引導(dǎo)頁(yè)面" 
    expect(guideIsExist).to be false
    sleep(2)
end
  • 打開(kāi)終端,運(yùn)行cucumber --tags @guidepage效果,我這里是按照tags來(lái)運(yùn)行的。
play.png

這里所有用到的id都是需要項(xiàng)目源碼里面去設(shè)置accessibilityLabel屬性的

// 例如引導(dǎo)頁(yè)和最后一頁(yè)的兩個(gè)按鈕的id設(shè)置為:
guideView.accessibilityLabel = "Guide_Page_View"
guideView.logtinButton.accessibilityLabel = "Guide_Login_Btn"
guideView.startButton.accessibilityLabel  = "Guide_Start_Btn"

// 登錄界面
view.accessibilityLabel = "login_page"

如果某些頁(yè)面定位不到可以設(shè)置屬性isAccessibilityElement為true

以上手動(dòng)添加屬性(比較笨),這里有大神已經(jīng)造好的輪子:給UI控件添加自動(dòng)化測(cè)試的標(biāo)簽拿走。

(9)、元素定位、常用事件和斷言等

元素定位

# 1、使用button查找按鈕
first_button // 查找第一個(gè)button
button(value) // 查找第一個(gè)包含value的button,返回[UIAButton|XCUIElementTypeButton]對(duì)象
buttons(value) // 查找所有包含value的所有buttons,返回[Array<UIAButton|XCUIElementTypeButton>]對(duì)象
 
eg:
button("登錄") // 查找登錄按鈕

# 2、使用textfield查找輸入框
first_textfield // 查找第一個(gè)textfield
textfield(value) // 查找第一個(gè)包含value的textfield,返回[TextField]
 
eg:
textfield("用戶(hù)名") // 查找

# 3、使用accessibility_id查找
id(value) // 返回id等于value的元素
 
eg:
id("登錄") // 返回登錄按鈕
id("登錄頁(yè)面") // 返回登錄頁(yè)面

# 4、通過(guò)find查找
find(value) // 返回包含value的元素
find_elements(:class, 'XCUIElementTypeCell') // 通過(guò)類(lèi)名查找
 
eg:
find("登錄頁(yè)面")

# 5、通過(guò)xpath查找
xpath(xpath_str)

# web元素定位:
# 測(cè)試web頁(yè)面首先需要切換driver的上下文
web = driver.available_contexts[1]
driver.set_context(web)

# 定位web頁(yè)面的元素
driver.find_elements(:css, ".re-bb") # 通過(guò)類(lèi)選擇器.re-bb定位css的元素

更多元素定位語(yǔ)法

常用事件

// 通過(guò)坐標(biāo)點(diǎn)擊
tap(x: 68, y: 171)
 
// 通過(guò)按鈕元素點(diǎn)擊
button("登錄").click

// 滑動(dòng)手勢(shì)
swipe(direction:, element: nil) // direction - Either 'up', 'down', 'left' or 'right'.
 
eg: 上滑手勢(shì)
swipe(direction: "up", element: nil)

// wait
wait { find("登錄頁(yè)面") } // 等待登錄頁(yè)面加載完成
 
// sleep
sleep(2) // 延時(shí)2秒

更多點(diǎn)擊事件

斷言

# 1. 相等
expect(actual).to eq(expected)  # passes if actual == expected
expect(actual).to eql(expected) # passes if actual.eql?(expected)
expect(actual).not_to eql(not_expected) # passes if not(actual.eql?(expected))

# 2、比較
expect(actual).to be >  expected
expect(actual).to be >= expected
expect(actual).to be <= expected
expect(actual).to be <  expected
expect(actual).to be_within(delta).of(expected)

# 3、類(lèi)型判斷
expect(actual).to be >  expected
expect(actual).to be >= expected
expect(actual).to be <= expected
expect(actual).to be <  expected
expect(actual).to be_within(delta).of(expected)

#  4、Bool值比較
expect(actual).to be_truthy   # passes if actual is truthy (not nil or false)
expect(actual).to be true     # passes if actual == true
expect(actual).to be_falsy    # passes if actual is falsy (nil or false)
expect(actual).to be false    # passes if actual == false
expect(actual).to be_nil      # passes if actual is nil
expect(actual).to_not be_nil  # passes if actual is not nil

# 5、錯(cuò)誤
expect { ... }.to raise_error
expect { ... }.to raise_error(ErrorClass)
expect { ... }.to raise_error("message")
expect { ... }.to raise_error(ErrorClass, "message")

# 6、異常
expect { ... }.to throw_symbol
expect { ... }.to throw_symbol(:symbol)
expect { ... }.to throw_symbol(:symbol, 'value')

更多斷言語(yǔ)法

其它

可通過(guò)methods方法,查看元素所有可用的屬性和方法

e.g. :
并且(/^點(diǎn)擊返回$/) do
  puts driver.methods
end

輸出結(jié)果為:
[:network_connection_type, :network_connection_type=, :location, :location=, 
:set_location, :touch, :lock, :unlock, :reset, :window_size, :shake, :launch_app, :close_app, :device_locked?, :device_time, :current_context, :open_notifications, 
:toggle_airplane_mode, :current_activity, :current_package, :get_system_bars, :get_display_density, :is_keyboard_shown, :get_network_connection, 
:get_performance_data_types, :available_contexts, :set_context, :app_strings, 
:install_app, :remove_app, :app_installed?, :background_app, :hide_keyboard, 
:press_keycode, :long_press_keycode, :set_immediate_value, :push_file, :pull_file, 
:pull_folder, :get_settings, :update_settings, :touch_actions, :multi_touch, :touch_id, 
:toggle_touch_id_enrollment, :ime_deactivate, :ime_activate, :ime_available_engines, :ime_active_engine, :ime_activated, :find_element, :find_elements, :local_storage, 
:session_storage, :remote_status, :rotate, :rotation=, :orientation, :session_id, 
:save_screenshot, :screenshot_as, :file_detector=, :[], :inspect, :first, :close, :all, 
:action, :quit, :get, :ref, :title, :script, :window_handle, :window_handles, :mouse, 
:keyboard, :browser, :navigate, :switch_to, :manage, :current_url, :page_source, 
:execute_script, :execute_async_script, :capabilities, :methods, :singleton_methods,
 :protected_methods, :private_methods, :public_methods, :to_yaml, 
:to_yaml_properties, :psych_to_yaml, :cucumber_instance_exec, :to_json, 
:instance_of?, :public_send, :instance_variable_get, :instance_variable_set, 
:instance_variable_defined?, :remove_instance_variable, :kind_of?, :instance_variables, :tap, :method, :public_method, :singleton_method, 
:awesome_print, :is_a?, :extend, :define_singleton_method, :awesome_inspect, 
:to_enum, :enum_for, :ai, :<=>, :===, :=~, :!~, :eql?, :respond_to?, :freeze, :object_id, :display, :send, :gem, :to_s, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, 
:taint, :tainted?, :untaint, :untrust, :trust, :untrusted?, :frozen?, :!, :==, :!=, :send, :equal?, :instance_eval, :instance_exec, :id, :should, :should_not]

6.借助Appium來(lái)進(jìn)行元素定位,步驟如下:

Appium客服端點(diǎn)擊搜索按鈕
配置運(yùn)行的信息
元素定位

7.源碼地址

Swift項(xiàng)目源碼

測(cè)試腳本項(xiàng)目源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容