这是一篇由 Jérémy Pelé 撰写的客座文章
在 Chronogolf,我们已经使用 NativeScript 18 个月了,我一直对缺乏合适的端到端测试 (e2e) 解决方案感到沮丧。
随着我们的移动应用用户群迅速增长,测试变得至关重要,需要得到妥善解决。由于应用商店的限制,每个推送到生产环境的错误都需要在 2-3 天内解决。您最好确保您推送到生产环境的代码是正确的!
我们首先尝试了 functional-tests-core
,它在某种程度上有效,但配置繁重,规范编写起来很痛苦。然后我们转向了 nativescript-dev-appium
,这是一个好的开始,但在当时还没有完全准备好(我们说的是 2017 年夏季开始)。
NativeScript 生态系统的一件好事:一切都在快速发展!我将向您展示一个完整的流程,用于使用 e2e 规范来覆盖您的应用。
我再次尝试了 nativescript-dev-appium
插件,并且很高兴地看到它现在比以前完整得多。
async
和 await
首先,安装 nativescript-dev-appium
及其依赖项。
1) 安装
npm install -g appium
npm install -D nativescript-dev-appium
您会发现一个生成的 e2e
文件夹。此文件夹将包含您的第一个规范文件以及 Appium 所需的配置文件夹。
2) 配置
您需要定义将用于运行套件的设备。
请注意,当在测试中使用图像比较时,每个设备都需要获取自己的一组资产(因为屏幕大小、填充等可能会根据屏幕分辨率而有所不同)。根据您的存储方式,这可能难以维护,并且会增加您的仓库大小。
为了简单起见,我们选择使用一个模拟器 (iOS) 和一个模拟器 (Android)。
打开 e2e/config/appium.capabilities.json
并定义您的设置。
{
"sim.iPhone7": {
"platformName": "iOS",
"platformVersion": "11.2",
"deviceName": "iPhone 7",
"fullReset": true, // Device will be destroyed and fresh install will be made after each suite
"app": ""
}
}
要使用上述功能配置运行套件,您将使用以下命令
npm run e2e -- --runType sim.iPhone7 --reuseDevice
现在让我们开始编写一些测试...
每个套件都将按照相同的模式编写:需要初始化一个新的 AppiumDriver
单例,所有测试都将依赖于它。
describe('My Suite', () => {
let driver: AppiumDriver
before(async () => {
// Wait for the driver instance to be created
driver = await createDriver()
})
after(async () => {
// Destroy the driver instance
await driver.quit()
})
it('validates something', async () => {
// write your test in here
})
})
给定一个简单的按钮,我将向您展示如何使用不同的选择器类型来选择它。
<Button automationText="loginFormSubmit"
class="submit btn-primary btn-rounded-sm"
text="Submit" (tap)="onSubmit()">
</Button>
XPath (findElementByXPath, findElementsByXPath)
let submitBtn
if (driver.isAndroid) {
submitBtn = await driver.findElementByXPath('//android.widget.Button')
} else {
submitBtn = await driver.findElementByXPath('//XCUIElementTypeButton') // ios 11
submitBtn = await driver.findElementByXPath('//UIAButton') // ios
}
优点
缺点
难以编写和阅读。如果更新模板或组件,则可能需要更新规范(因为路径也可能发生变化)。
// 您还可以使用相对于平台抽象映射的驱动程序定位器 let submitBtn = await driver.findElementByXPath(//${driver.locators.button}
)
文本 (findElementByText, findElementsByText) - 包含/完全匹配
const submitBtn = await driver.findElementByText('Submit', SearchOptions.exact)
await submitBtn.click()
优点
缺点
组件类型 (findElementByClassName, findElementsByClassName) - 目标 NativeScript 组件:标签、按钮、网格布局...
缺点:过于通用,随着布局随时间推移而变化,无法扩展。
// 直接使用类名类型 const submitBtn = await driver.findElementByClassName('button') await submitBtn.click()
// 使用驱动程序定位器辅助函数的替代方法 const submitBtn = await driver.findElementByClassName(driver.locators.button) await submitBtn.click()
注意:如果驱动程序在页面上找到多个按钮,则将选择第一个按钮。您可以将页面上的所有按钮选择为 UIElement 数组,然后根据其索引访问所需的按钮。
const submitBtns = await driver.findElementsByClassName('button')
await submitBtns[0].click()
辅助功能 ID (findElementByAccessibilityId, findElementsByAccessibilityId)
automationText="loginPasswordInput"
(因为 automationText 是辅助功能,自定义组件将不起作用。您需要将该选项作为输入传递,它将放置在自定义组件模板的根目录上)。优点
const submitBtn = await driver.findElementByAccessibilityId('loginFormSubmit') await submitBtn.click()
主要有两种类型的规范
基于图像比较
屏幕截图
it('compare screen', async () => {
assert.isTrue(await driver.compareScreen('my-whole-screen'))
})
元素
it('compare button element', async () => {
const submitBtn = await driver.findElementByAccessibilityId('loginFormSubmit')
assert.isTrue(await driver.compareElement(submitBtn, 'my-submit-btn'))
})
基于元素
文本值
it('compare button element', async () => {
const submitBtn = await driver.findElementByAccessibilityId('loginFormSubmit')
const submitBtnText = submitBtn.text()
assert.isEqual(submitBtnText, 'Submit')
})
组件显示/隐藏?
it('compare button element', async () => {
const submitBtn = await driver.findElementByAccessibilityId('loginFormSubmit')
assert.isTrue(await submitBtn.isDisplayed())
})
太棒了!您可以开始在本地测试您的应用了 :)
npm run e2e -- --runType sim.iPhone7 --reuseDevice
我们如何自动化此流程?我们将使用持续集成工具(此处为 CircleCI),而不是手动构建和触发套件。
CircleCI 提供 macOS 虚拟机,这意味着我们将能够使用它们在云中构建和测试我们的应用。您需要将您的 Git 仓库连接到 CircleCi 帐户,其余的只需要配置。
您需要在仓库的根目录中放置一个 circle.yml
文件。此文件将包含需要在虚拟机上执行的不同执行。在下面的示例中,我们希望配置一台具有所有依赖项的新机器,构建应用并在其上运行套件。
machine:
xcode:
version: 9.0.1
dependencies:
cache_directories:
- ~/.npm
- ~/Library/Caches/Homebrew
- ~/Library/Caches/CocoaPods
pre:
# Fetch cocoapods specs from S3 instead of github
- curl -sS https://cocoapods-specs.circleci.com/fetch-cocoapods-repo-from-s3.sh | bash
- npm i -g nativescript --ignore-scripts
override:
- npm set progress=false && npm install
post:
# Pre-start emulator you'll use for the specs
- xcrun instruments -w "iPhone 7 (11.0.1) [":
background: true
compile:
override:
# Build Test App
- tns prepare ios || echo "ios prepare"
- tns build ios
test:
override:
# E2E
- npm run e2e -- --runType sim.iPhone7
post:
# Export results as artifacts downloadable
- mv e2e/reports/**/* $CIRCLE_ARTIFACTS/
general:
branches:
only: # list of branches to listen to
- master
- develop
由于您始终从一个全新的环境进行测试,因此您可以确保从头开始构建一个干净的应用(没有遗留的旧的转译后的 .js 文件,没有遗漏的文件等)。
如果要使用本地服务器测试应用,可以对其进行配置并在同一虚拟机上启动它。只需创建您自己的序列并将其添加到上面的示例中即可。
NativeScript 建议使用 Travis CI 测试您的插件。它可能是 Circle CI 的一个不错的替代方案。
我个人没有使用过它,但您可以看看 nativescript-facebook 是如何做的。
您还可以选择一个不提供模拟器/模拟器的持续集成系统,并将其与 Sauce Labs 服务结合使用。
Sauce Labs 允许您将应用上传到其平台,并使用其云模拟器运行测试套件。
nativescript-dev-appium
在运行套件规范时使用 --sauceLab
选项支持 Sauce Labs。
1) 配置
{
"local.sim.iPhone7": {
"platformName": "iOS",
"platformVersion": "11.2",
"deviceName": "iPhone 7",
"fullReset": true,
"app": ""
},
"sauce.sim.iPhone7": {
"platformName": "iOS",
"platformVersion": "11.2",
"deviceName": "iPhone 7 Simulator",
"appium-version": "1.7.2",
"app": ""
}
}
功能以 local
或 sauce
为前缀,以保持我们正在使用什么的清晰上下文。这显然是可选的,您可以选择此处要使用的模式。
请注意添加了
"appium-version": "1.7.2"
。
由于 Sauce Labs 可能使用与本地机器模拟器/模拟器不同的设备名称,请参考 Sauce Labs 平台配置器。
https://wiki.saucelabs.com/display/DOCS/Platform+Configurator#/
构建您的应用并将其打包为归档文件
tns build ios && zip -r mobileapp.zip platforms/ios/build/emulator/myapp.app
将其导出到 Sauce Labs 存储
curl -v -u $SAUCE_USER:$SAUCE_KEY -X POST "http://saucelabs.com/rest/v1/storage/$SAUCE_USER/myapp.zip?overwrite=true" -H "Content-Type: application/octet-stream" --data-binary @myapp.zip
在您刚刚上传的 Sauce Labs 归档文件上运行套件
npm run e2e -- --runType sauce.sim.iPhone7 --sauceLab --appPath myapp.zip
如果需要在 Sauce Labs 上使用 API 服务器测试我的应用怎么办?
Sauce Labs 提供了一个名为 Sauce Connect
的工具,该工具在运行该工具的机器和测试应用的 Sauce Lab 虚拟机之间创建隧道。
这意味着 Sauce Labs 虚拟机将能够访问 http://localhost:3000
(或您用于运行服务器的任何地址)。