Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec6a90a797 | ||
|
|
2fb08bbd7f | ||
|
|
c5bcd82263 | ||
|
|
7f8e62e983 | ||
|
|
8fa1cee8af | ||
|
|
07a854acd2 | ||
|
|
cfb1401d0f | ||
|
|
39b8fd5476 | ||
|
|
ee00285b38 | ||
|
|
b514971e99 | ||
|
|
549defda91 | ||
|
|
11c2c802ed | ||
|
|
fc23e07348 | ||
|
|
b2233e84a1 | ||
|
|
d8d799a19c | ||
|
|
c3d328cb81 | ||
|
|
7f57f4ec45 | ||
|
|
5ed2e36b47 | ||
|
|
ab2245dcfb | ||
|
|
94bcad8570 | ||
|
|
a6f45e5529 | ||
|
|
59222ca0d3 | ||
|
|
6b9c778d35 | ||
|
|
8cb6df6952 | ||
|
|
26bcc8db70 | ||
|
|
b81d43978a | ||
|
|
7095d9e7e6 | ||
|
|
d414b6e8ed | ||
|
|
5a09fdf722 | ||
|
|
bdc9bcbd02 | ||
|
|
f59e79d649 | ||
|
|
589d776ab1 | ||
|
|
07787168a7 | ||
|
|
4839419748 | ||
|
|
756688bddb | ||
|
|
55a5f6eac5 | ||
|
|
40208145c9 | ||
|
|
4eae53f1b8 | ||
|
|
ceb70e1950 | ||
|
|
314c597e24 | ||
|
|
2ad262b775 | ||
|
|
6c769a2b7c | ||
|
|
25cc27d161 | ||
|
|
2955c172b1 | ||
|
|
d3c11e364d | ||
|
|
a2654762b6 | ||
|
|
c20a13ea95 | ||
|
|
de428c60c8 | ||
|
|
77bf18def6 | ||
|
|
fbe864f746 | ||
|
|
9b9f7e9acf | ||
|
|
023ec51a0c | ||
|
|
e512c87109 | ||
|
|
eff94987da | ||
|
|
256c2ad200 | ||
|
|
04d11619cb | ||
|
|
38ebd45b51 | ||
|
|
487bfd30c2 | ||
|
|
17a2b8cad0 | ||
|
|
5db7517cee | ||
|
|
b74612cae8 | ||
|
|
88de3b3906 | ||
|
|
1704eb5513 | ||
|
|
91d47496ef | ||
|
|
47a0e66dbd | ||
|
|
a461c58f2b | ||
|
|
5109e6852b | ||
|
|
1cdcba2e02 | ||
|
|
55efe2c411 | ||
|
|
ad76ffc1a8 | ||
|
|
f11a34f7af | ||
|
|
b78a8f4deb | ||
|
|
2bd49ef5fa | ||
|
|
e00fe2644b | ||
|
|
9295d0c682 | ||
|
|
2ffe27730b | ||
|
|
81a2bfe141 | ||
|
|
cece26cbb3 | ||
|
|
993ad769ad | ||
|
|
7a7049fa51 | ||
|
|
4ce589030a | ||
|
|
ce47f82d17 | ||
|
|
1e7026a532 | ||
|
|
9331969c23 | ||
|
|
950372c1bd | ||
|
|
c66144d62b | ||
|
|
385e5c5b23 | ||
|
|
254cc638c5 | ||
|
|
21e8d60a5c | ||
|
|
52f4fc6a2c | ||
|
|
01abeac864 | ||
|
|
7b38ac9417 | ||
|
|
4def2e926a | ||
|
|
9471f13270 |
@@ -10,18 +10,19 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x, 14.x]
|
||||
node-version: [12.x, 14.x, 16.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm install
|
||||
- run: npm test
|
||||
- run: npm run install-fake-driver
|
||||
- run: npm run e2e-test
|
||||
- run: |
|
||||
npm install
|
||||
npm test
|
||||
npm run install-fake-driver
|
||||
npm run e2e-test
|
||||
|
||||
generate_docs:
|
||||
needs: [build]
|
||||
@@ -29,9 +30,6 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- run: npm install
|
||||
- run: npm run generate-docs
|
||||
- run: |
|
||||
npm install
|
||||
npm run generate-docs
|
||||
|
||||
@@ -54,3 +54,5 @@ sample-code/csharp/test/bin
|
||||
sample-code/csharp/packages
|
||||
appium.zip
|
||||
.vscode/settings.json
|
||||
.nyc_output
|
||||
*.lerna_backup
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// @ts-check
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
require: require.resolve('./test/setup.js')
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (wallaby) => {
|
||||
return {
|
||||
compilers: {
|
||||
'**/*.js': wallaby.compilers.babel(),
|
||||
},
|
||||
debug: true,
|
||||
env: {
|
||||
type: 'node',
|
||||
},
|
||||
files: [
|
||||
'./packages/**/*.js',
|
||||
'./packages/**/*.json',
|
||||
'!./packages/**/build/**',
|
||||
'!./packages/**/test/**/*-specs.js',
|
||||
'!./packages/*/node_modules/**',
|
||||
'!./packages/*/gulpfile.js',
|
||||
// below this are fixtures
|
||||
{
|
||||
binary: true,
|
||||
pattern: './packages/support/test/assets/sample_binary.plist',
|
||||
},
|
||||
{
|
||||
instrument: false,
|
||||
pattern: './packages/support/test/assets/sample_text.plist',
|
||||
},
|
||||
{
|
||||
instrument: false,
|
||||
pattern: './packages/*/test/**/fixtures/**/*',
|
||||
},
|
||||
{
|
||||
instrument: false,
|
||||
pattern: './packages/base-driver/static/**/*',
|
||||
},
|
||||
{
|
||||
instrument: false,
|
||||
pattern: './packages/gulp-plugins/build/**/*',
|
||||
},
|
||||
{
|
||||
instrument: false,
|
||||
pattern: './test/setup.js'
|
||||
}
|
||||
],
|
||||
testFramework: 'mocha',
|
||||
tests: [
|
||||
'./packages/*/test/**/*-specs.js',
|
||||
'!./packages/*/test/**/*-e2e-specs.js',
|
||||
'!./packages/*/node_modules/**',
|
||||
// this is more of an E2E test and it's tedious to run
|
||||
'!./packages/gulp-plugins/test/transpile-specs.js',
|
||||
],
|
||||
workers: {
|
||||
restart: true,
|
||||
},
|
||||
setup() {
|
||||
require('./test/setup');
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -17,7 +17,8 @@
|
||||
"@babel/plugin-proposal-numeric-separator",
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
["@babel/plugin-proposal-private-methods", {"loose": true}],
|
||||
["@babel/plugin-proposal-class-properties", {"loose": true}]
|
||||
["@babel/plugin-proposal-class-properties", {"loose": true}],
|
||||
["@babel/plugin-proposal-private-property-in-object", {"loose": true}]
|
||||
],
|
||||
"comments": false,
|
||||
"sourceMaps": "both",
|
||||
@@ -27,10 +28,7 @@
|
||||
[
|
||||
"istanbul",
|
||||
{
|
||||
"exclude": [
|
||||
"test",
|
||||
"build"
|
||||
]
|
||||
"exclude": ["test", "build"]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
## Android 的 UiAutomator2 驱动
|
||||
|
||||
Appium 目前官方推荐使用 `UiAutomator2` 作为安卓app在自动化测试中的驱动。
|
||||
_(如果你是Appium新手? 可以参阅 [introduction to Appium drivers(TODO)](#TODO))_.
|
||||
这个驱动使用了谷歌的 [UiAutomator2](https://developer.android.com/training/testing/ui-automator.html) 技术,让自动化测试可以更方便地在安卓真机或者模拟器上运行。
|
||||
|
||||
UiAutomator2 驱动的开发过程,记录在了[appium-uiautomator2-driver](https://github.com/appium/appium-uiautomator2-driver) 代码仓库中。
|
||||
|
||||
在更早期的安卓设备驱动中,包括了以下驱动:
|
||||
* [UiAutomator 驱动](/docs/en/drivers/android-uiautomator.md)
|
||||
|
||||
|
||||
### 需求与依赖
|
||||
|
||||
在 Appium 常规依赖之外:
|
||||
|
||||
* 需要在您的设备上,安装并配置好 Java 8
|
||||
* 在 Mac,Windows 或者 Linux OS 设备上,需要安装好 Android SDK
|
||||
* Android SDK 中 Build Tools 的版本需要大于等于 24
|
||||
|
||||
此外,UiAutomator2 驱动是不支持对 Android 5.0 (Lollipop, API level 21)以下的设备进行自动化测试的,如果对这类版本的设备进行自动化测试,建议使用:[UiAutomator 驱动](/docs/en/drivers/android-uiautomator.md);
|
||||
|
||||
|
||||
### 使用方式
|
||||
|
||||
通过 UiAutomator2 驱动启动一个测试任务时,需要传递一个`automationName` [capability] 的参数在 [新会话的请求](#TODO) 中,并且 `automationName` 的值需要是:`UiAutomator2`。当然,你至少也需要再传递这些参数:`platformName` (=`Android`), `platformVersion`, `deviceName`, 与 `app`。
|
||||
|
||||
同时,强烈建议您传递:`appPackage` 与 `appActivity` 这两个参数,以便让 Appium 知道您想要拉起哪个 App 和哪个 activity。如果没有的话,Appium 将会自己从 App 的 manifest 文件中自动去查找。
|
||||
|
||||
|
||||
### 功能参数
|
||||
|
||||
UiAutomator2 驱动支持许多通用的功能参数:[Appium
|
||||
capabilities](/docs/en/writing-running-appium/caps.md),同时,有许多特有的功能参数,可以参考这篇文档查找这些功能参数:[Android
|
||||
section](/docs/en/writing-running-appium/caps.md#android-only)。
|
||||
|
||||
对于 web 测试,是测试 Chrome 而不是去测试您自己的应用程序,需要保证 `app` 参数为空,并且设置 `browserName` 为 `Chrome`。需要提醒您的是:您需要保证 Chrome 是在安卓模拟器/真机上运行,并且与 [version compatible with
|
||||
Chromedriver](/docs/en/writing-running-appium/web/chromedriver.md) 兼容。
|
||||
|
||||
|
||||
### 命令
|
||||
|
||||
如果需要查看 Appium 支持的命令,及它们是如何映射到 UiAutomator2 驱动的,可以参考:[API
|
||||
Reference](#TODO)。
|
||||
|
||||
|
||||
### 基础设置
|
||||
|
||||
1. 确保您拥有 Appium 的基础依赖(例如:Node 与 NPM)来安装和配置 Appium。
|
||||
|
||||
1. 确保 Java ( 这里指的是 JDK 而不是 JRE )已经成功安装并且在环境变量中配置好了 Java 的路径。这一步的具体操作,在 Mac/Linux 与 Windows 等不同平台上操作不同,这里为您提供一个在 Windows 平台上设置路径的方法:[点击查看](https://www.java.com/en/download/help/path.xml)。
|
||||
|
||||
1. 确保环境变量: `JAVA_HOME` 已经成功设置为 JDK 的路径。举一个 Mac/Linux 的例子(这个路径的具体地址,根据您安装的实际情况有所不同):
|
||||
```
|
||||
export JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home"
|
||||
```
|
||||
|
||||
在 Windows 电脑上,可以通过 「控制面板」来配置。
|
||||
[Android Studio](https://developer.android.com/studio/index.html) 同样提供了一个 JDK,像这样的一个路径: `/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home` (示例为Mac电脑版本)。您同样可以指定这个路径。
|
||||
|
||||
1. 安装 [Android SDK](http://developer.android.com/sdk/index.html)。当前最常用的方法是通过:[Android
|
||||
Studio](https://developer.android.com/studio/index.html) 来安装配置。可以在桌面应用程序中,通过GUI界面操作,选中一个路径来下载 Android SDK。
|
||||
|
||||
1. 设置 `ANDROID_HOME` 环境变量。举个例子,如果您下载 Android SDK 在 `/usr/local/adt` 这个路径下,您将在这个路径下,找到一个包含 SDK 文件的、名称为: `sdk` 的文件夹。在这种情况下,打开
|
||||
Mac 和 Linux 的配置文件(如:`~/.bashrc`, `~/.bash_profile` 及其他类型的文件),将这个环境变量与路径记录在其中。
|
||||
|
||||
```
|
||||
export ANDROID_HOME="/usr/local/adt/sdk"
|
||||
```
|
||||
|
||||
在 Windows 机器上,按照相同的步骤,将环境变量设置在「控制面板」中。
|
||||
|
||||
1. 通过 SDK manager,确保您安装了您所需要的、对应 Android API 版本的 SDK(例如:24)。
|
||||
|
||||
1. 在 Windows 电脑上,需确保您的 Appium 是通过管理员模式来运行的。
|
||||
|
||||
到此,您基础的系统配置已经完成了。接下来的步骤,需要看您是选择在模拟器运行测试还是在真机上运行测试。需要准备好待测 App (最好是Debug 版本)APK 的本地路径或下载链接,这个路径或链接,在运行测试时,需要设置为 `app` 参数的内容。
|
||||
|
||||
|
||||
### 模拟器设置
|
||||
|
||||
使用模拟器运行测试是,需要使用 Android Studio 或 SDK 提供的 AVD Manager。通过使用这个工具,来创建您所需要的模拟器。在模拟器启动时,Appium 将会自动找到并使用它进行测试。同时,如果您指定了 `avd` 这个功能参数为您所制定的设备名称时,Appium 会启动您指定的模拟器来运行测试。
|
||||
|
||||
关于模拟器的一些小提示:
|
||||
|
||||
* Android 模拟器可以设置硬件加速,尽管它有一些局限性。可以通过 Intel 网站下载,或者通过 Android SDK manager。了解更多信息,[可以点击这里查看](https://github.com/intel/haxm)。
|
||||
|
||||
* 如果您想在 Appium 测试中使用电源相关的命令,请确保在 AVD 的 `config.ini` 文件中,设置了:`hw.battery=yes` (在 Android 5.0 上,为默认设置)。
|
||||
|
||||
|
||||
### 真机设置
|
||||
|
||||
对于 Android 自动化测试,除了以下一些简单操作外,不需要其他复杂的设置:
|
||||
|
||||
* 确保打开了设备的 [开发者模式](https://developer.android.com/studio/debug/dev-options.html)。
|
||||
|
||||
* 确保设备通过 USB 连接到 Appium 服务启动的设备上,并且可以被 [ADB](https://developer.android.com/studio/command-line/adb.html) 找到
|
||||
( 执行 `adb devices` 确认 )。
|
||||
|
||||
* 确保设置中的“验证应用程序”是关闭的,以允许 Appium 安装一些辅助软件,而不用手动点击确认。
|
||||
|
||||
( 对于一些特殊的指令,设备可能需要被 root ,尽管这并不是经常会遇到的。 )
|
||||
|
After Width: | Height: | Size: 832 KiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 765 KiB |
|
After Width: | Height: | Size: 928 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 650 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 849 KiB |
@@ -0,0 +1,200 @@
|
||||
## Appium XCUITest 驱动在真机上的设置
|
||||
|
||||
### 安装依赖
|
||||
|
||||
自 Appium 1.15.0 起,Appium 与真机设备通过 [appium-ios-device](https://github.com/appium/appium-ios-device) 进行联通。您不需要再额外安装依赖项。XCUITest 驱动会安装一个名为 `WebDriverAgent-Runner` 的辅助软件在设备上,并通过这个辅助软件控制被测应用。虽然这听起来很简单,但在苹果软件的代码签名和配置应用程序上,在开发和测试中可能会有些头痛。
|
||||
应该可以从Xcode中找到待测设备。请确保被测设备连接Xcode万无一失。你可以通过打开 _Devices and Simulators_ 这个选项栏来确认这件事。
|
||||
|
||||
这篇文章也有利于解决环境依赖安装的问题 [appium-xcuitest-driver](https://github.com/appium/appium-xcuitest-driver) 。
|
||||
|
||||
|
||||
### 基础 (自动) 配置
|
||||
|
||||
在 iOS 真机上使用 XCUITest 运行自动化测试最简单的方法,就是使用自动配置来运行。这里有两条路:
|
||||
|
||||
* 使用功能参数 `xcodeOrgId` 与 `xcodeSigningId` :
|
||||
```json
|
||||
{
|
||||
"xcodeOrgId": "<Team ID>",
|
||||
"xcodeSigningId": "iPhone Developer"
|
||||
}
|
||||
```
|
||||
* 在任意路径下,创建一个 `.xcconfig` 文件,并添加如下配置:
|
||||
```
|
||||
DEVELOPMENT_TEAM = <Team ID>
|
||||
CODE_SIGN_IDENTITY = iPhone Developer
|
||||
```
|
||||
|
||||
无论哪种情况,苹果公司会生成一个 10 位字符的唯一字符串作为您的 Team ID,分配给您的开发团队。您可以您的开发者账号找到这个 Team ID,您可以登录[developer.apple.com/account](http://developer.apple.com/account) 并点击侧边栏的 「开发者资格( Membership )」。您可以在 「开发者资格( Membership )」 详细信息的团队名称下找到您的 Team ID。您也可以在您电脑「钥匙串」中的 iPhone 开发者证书中的 「组织单元("Organizational Unit")」字段中,找到您的 Team ID。
|
||||
|
||||
需要注意的是,使用 `xcodeConfigFile` 这种方式,与使用 `xcodeOrgId` 和 `xcodeSigningId` 的方式是互斥的。
|
||||
|
||||
这些配置完成后,您就只剩下将您的真机设备配置在参数 `udid` 中就可以了。
|
||||
|
||||
如果配置完无法运行,一般会在 Appium 服务日志中有对应的报错信息,比如有些会提示:`info XCUITest xcodebuild exited with code '65' and
|
||||
signal 'null'`。这通常意味着代码签名配置的有问题。继续回看上一部分[基础 (自动) 配置](#basic-manual-configuration)来解决这个问题。
|
||||
|
||||
如果 `WebDriverAgentRunner` 在真机设备上安装成功,但是 Appium 服务日志有如下报错:
|
||||
```
|
||||
2017-01-24 09:02:18.358 xcodebuild[30385:339674] Error Domain=com.apple.platform.iphoneos Code=-12 "Unable to launch com.apple.test.WebDriverAgentRunner-Runner" UserInfo={NSLocalizedDescription=Unable to launch com.apple.test.WebDriverAgentRunner-Runner, NSUnderlyingError=0x7fa839cadc60 {Error Domain=DTXMessage Code=1 "(null)" UserInfo={DTXExceptionKey=The operation couldn’t be completed. Unable to launch com.apple.test.WebDriverAgentRunner-Runner because it has an invalid code signature, inadequate entitlements or its profile has not been explicitly trusted by the user. : Failed to launch process with bundle identifier 'com.apple.test.WebDriverAgentRunner-Runner'}}}
|
||||
2017-01-24 09:02:18.358 xcodebuild[30385:339674] Error Domain=IDETestOperationsObserverErrorDomain Code=5 "Early unexpected exit, operation never finished bootstrapping - no restart will be attempted" UserInfo={NSLocalizedDescription=Early unexpected exit, operation never finished bootstrapping - no restart will be attempted}
|
||||
|
||||
Testing failed:
|
||||
Test target WebDriverAgentRunner encountered an error (Early unexpected exit, operation never finished bootstrapping - no restart will be attempted)
|
||||
```
|
||||
这个问题是由于开发者在设备上不受信任,当你手动尝试将 `WebDriverAgentRunner` 安装在您的真机设备上时,会看到如下弹窗信息:
|
||||
|
||||

|
||||
|
||||
您可以通过 设置(Settings) => 通用(General) => 设备管理(Device Management)来信任开发者,以使 `WebDriverAgentRunner` 能够成功运行 (更多详细信息见: [苹果官方支持文档](https://support.apple.com/en-us/HT204460))。
|
||||
|
||||
|
||||
|
||||
### 基础 (手动) 配置
|
||||
|
||||
在很多情况下,基本的自动配置是不满足使用需要的。通常在自动化运行时,会有代码签名和项目配置的问题。当正在使用的开发帐户是“免费”帐户时,无法创建出通用的配置文件。
|
||||
|
||||
这通常表现为 Xcode 报错为:**failed to create
|
||||
provisioning profile**:
|
||||
|
||||

|
||||
|
||||
解决此问题的最简单方法是通过打开 [Xcode](https://developer.apple.com/xcode/) 并创建一个新项目来创建配置文件 :
|
||||
|
||||

|
||||
|
||||
只要选择了 "iOS",具体的应用类型无关紧要,一般使用单页面应用 ("Single View Application")最简单也最方便:
|
||||
|
||||

|
||||
|
||||
重要的是,使用一个唯一的 产品名称("Product Name")与 组织名称("Organization Name"),并且,在这时候,请选择你的团队("Team")。
|
||||
|
||||

|
||||
|
||||
您可以通过查看 "项目" 选项,来确认配置文件是否已创建:
|
||||
|
||||

|
||||
|
||||
或者通过进入您的帐户首选项并查看配置文件:
|
||||
|
||||

|
||||
|
||||
此时,您有一个有效的配置文件。记住 bundle id 并添加到测试的 `updatedWDABundleId` 功能参数中。然后按照 [如何初始化自动配置](#basic-automatic-configuration).
|
||||
|
||||
|
||||
### 完全手工配置
|
||||
|
||||
或者,配置文件可以完全通过手工在项目中配置(需要知道的是,每次WDA更新、Appium更新时,都需要操作一次,_不推荐_ ):
|
||||
|
||||
* 找到您 Appium 的安装路径:
|
||||
```
|
||||
$ which appium
|
||||
/path/where/installed/bin/appium
|
||||
```
|
||||
* 由于路径在: `/path/where/installed/bin/appium`,那么,`WebDriverAgent` 可以在这个路径的下级路径中被找到: `/path/where/installed/lib/node_modules/appium/node_modules/appium-webdriveragent`。
|
||||
打开终端(terminal),移动到该路径下执行如下命令:
|
||||
```
|
||||
mkdir -p Resources/WebDriverAgent.bundle
|
||||
```
|
||||
* ( 如果 Appium 版本低于 1.20 ) 在终端(terminal)执行如下命令:
|
||||
```
|
||||
./Scripts/bootstrap.sh -d
|
||||
```
|
||||
* Xcode 打开 `WebDriverAgent.xcodeproj`。同时选中 `WebDriverAgentLib`
|
||||
and `WebDriverAgentRunner` ,在 通用("General")页签下,都选择为自动管理签名("Automatically manage signing"),然后选择您所在的开发组织(`Development Team`),它将自动选择开发者签名证书(`Signing Ceritificate`)。结果应如下所示:
|
||||
|
||||

|
||||
|
||||
* Xcode 可能无法为 `WebDriverAgentRunner` 创建配置文件:
|
||||
|
||||

|
||||
|
||||
* 这需要通过进入构建设置("Build Settings")页签下,手动更改目标的bundle id,并且将构建产物标识符("Product Bundle Identifier")`com.facebook.WebDriverAgentRunner` 进行一些修改,以让 Xcode 通过:
|
||||
|
||||

|
||||
|
||||
* Going back to the "General" tab for the `WebDriverAgentRunner` target, you
|
||||
should now see that it has created a provisioning profile and all is well:
|
||||
返回 `WebDriverAgentRunner` 的通用("General")页签,您将看到配置文件已经创建完成,并且所有配置都是正常的了:
|
||||
|
||||

|
||||
|
||||
* 最后,您可以验证一切是否正常。构建项目:
|
||||
```
|
||||
xcodebuild -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination 'id=<udid>' test
|
||||
```
|
||||
如果一切正常,输出信息一般以此结尾:
|
||||
```
|
||||
Test Suite 'All tests' started at 2017-01-23 15:49:12.585
|
||||
Test Suite 'WebDriverAgentRunner.xctest' started at 2017-01-23 15:49:12.586
|
||||
Test Suite 'UITestingUITests' started at 2017-01-23 15:49:12.587
|
||||
Test Case '-[UITestingUITests testRunner]' started.
|
||||
t = 0.00s Start Test at 2017-01-23 15:49:12.588
|
||||
t = 0.00s Set Up
|
||||
```
|
||||
* 要完全验证,您可以尝试访问 WebDriverAgent 服务器状态
|
||||
(**提示:** 您 _必须_ 保证 WebDriverAgent 服务与设备在同一网络下,可以通过以下方式获取 IP, Settings => Wi-Fi => Current Network):
|
||||
```
|
||||
export DEVICE_URL='http://<device IP>:8100'
|
||||
export JSON_HEADER='-H "Content-Type: application/json;charset=UTF-8, accept: application/json"'
|
||||
curl -X GET $JSON_HEADER $DEVICE_URL/status
|
||||
```
|
||||
您将获得类似如下的返回信息:
|
||||
|
||||
```
|
||||
{
|
||||
"value" : {
|
||||
"state" : "success",
|
||||
"os" : {
|
||||
"name" : "iOS",
|
||||
"version" : "10.2"
|
||||
},
|
||||
"ios" : {
|
||||
"simulatorVersion" : "10.2",
|
||||
"ip" : "192.168.0.7"
|
||||
},
|
||||
"build" : {
|
||||
"time" : "Jan 23 2017 14:59:57"
|
||||
}
|
||||
},
|
||||
"sessionId" : "8951A6DD-F3AD-410E-A5DB-D042F42F68A7",
|
||||
"status" : 0
|
||||
}
|
||||
```
|
||||
|
||||
### 对被测应用的设置
|
||||
|
||||
不仅是 WDA 可以在您的设备上运行,被测应用也需要可以在您的设备上运行。它们之间的规则是相同的:一个拥有签名的被测应用( 一个 `.ipa` 文件)。 可以在 这里 [这里](https://medium.com/ios-os-x-development/ios-code-signing-provisioning-in-a-nutshell-d5b247760bef#.5hirl92tn)
|
||||
与
|
||||
[这里](https://www.nodesagency.com/understanding-code-signing-for-ios-apps) 找到更详细的描述。
|
||||
|
||||
更详细地说,要使用真机运行自动化测试,您需要以下内容:
|
||||
|
||||
* 一个 [苹果开发者ID](https://developer.apple.com/programs/ios/) 和一个有效的开发者帐户,以及一个配置好的开发证书和配置文件。(请确保配置文件具有[iOS Distribution](https://developer.apple.com/support/certificates/)类型)
|
||||
|
||||
* 如果需要在真机运行测试,一个合法的 iOS 开发者证书和配置文件是必须有的。您的 App 也需要签名。您可以在这里看到更多信息:[苹果官方文档](https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/TestingYouriOSApp/TestingYouriOSApp.html)。
|
||||
|
||||
* 一台 iPad 或 iPhone设备。请确保在 Xcode 中可以找到它们。
|
||||
|
||||
* 一个拥有签名的 `.app` 或 `.ipa` 被测 App 应用,或者是它们可以被构建的源代码。
|
||||
|
||||
* 一台拥有 [Xcode](https://developer.apple.com/xcode/) 与 Xcode 开发者命令行工具的 Mac 系统的电脑。
|
||||
|
||||
Appium 将通过 `ideviceinstaller` (`libimobiledevice` 的一部分功能) 将应用安装到设备上,但有时候,通过 Xcode 预安装会更加方便(再看看这个:[苹果官方文档](https://developer.apple.com/library/content/documentation/IDEs/Conceptual/AppDistributionGuide/TestingYouriOSApp/TestingYouriOSApp.html))
|
||||
|
||||
|
||||
### 问题排查思路
|
||||
|
||||
1. 请通过 Xcode Organizer 或 iTunes 确保设备 UDID 是正确的。 它是一个 20+ 的字符串。
|
||||
1. 请确保相同操作在模拟器上是可以运行的。
|
||||
1. 请确保在设备上 **启动** 了以下设置:
|
||||
1. 设置(Settings) -> 开发者(Developer) -> **启动 UI Automation (Enable UI Automation)**
|
||||
1. 设置(Settings) -> Safari -> 高级(Advanced) -> **Web Inspector** 与 **Remote Automation**
|
||||
1. 请阅读 [Automating mobile web apps](/writing-running-appium/web/mobile-web) 获取关于 WebView 运行的详细信息。
|
||||
1. 如果您不想为手动配置配置文件,请在自动生成时带上 `.xctrunner` 标识。`.xctrunner` 在 Xcode 11. [A reference](https://github.com/appium/appium/issues/13610) 起,将被自动配置。
|
||||
|
||||
1. 请确保设备没有越狱
|
||||
- `com.apple.mobile.installation_proxy` 服务在管理 App 上[不起作用](https://github.com/appium/appium-desktop/issues/1447)
|
||||
|
||||
1. 确保配置文件具有:[_iOS 分发证书_](https://developer.apple.com/support/certificates/)
|
||||
- 由于 Apple 的安全设计,活动 Xcode/xcodebuild 连接/会话是与 WebDriverAgentRunner 交互所必需需要的。 证书限制的影响: ([issue](https://github.com/appium/appium/issues/14577#issuecomment-660997827))
|
||||
@@ -0,0 +1,118 @@
|
||||
## iOS 的 XCUITest 驱动
|
||||
|
||||
iOS 应用的自动化测试,Appium 主要通过 `XCUITest` 驱动进行驱动。_(如果您是 Appium 新手? 可以参阅: [introduction to Appium drivers](#TODO))_。这个驱动通过苹果公司的 [XCUITest](https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/09-ui_testing.html) 库,让自动化测试可以更方便地执行 App 自动化测试。它是通过 [WebDriverAgent](https://github.com/facebook/webdriveragent) 服务对 XCUITest 进行访问。WebDriverAgent(也称之为 “ WDA ”)是 Facebook 公司的一个项目,Appium 的核心团队为其做出了许多贡献。WDA 是一个 WebDriver兼容的服务,服务可以运行在 iOS 模拟器或真机上,并且调用 XCUITest API。WDA 作为 Appium 的 XCUITest 驱动管理器,对用户而言是一个黑盒进程,通过 WDA 对往来的通信进行代理,并提供许多额外的能力(举个例子:如管理模拟器等方法)。
|
||||
|
||||
XCUITest 驱动的开发过程,记录在了 [appium-xcuitest-driver](https://github.com/appium/appium-xcuitest-driver) 仓库中。
|
||||
|
||||
|
||||
### 需求与依赖
|
||||
|
||||
在 Appium 常规依赖之外:
|
||||
|
||||
* 苹果公司从的 XCUITest 库只适用于 iOS 版本大于等于 iOS 9.3 的模拟器或真机设备。
|
||||
* 需要一台 macOS 10.11 或 10.12 的 Mac 电脑。
|
||||
* Xcode 7 或更高版本的 Xcode.
|
||||
* XCUITest 驱动从 Appium 1.6 开始在 Appium 中可用。
|
||||
* 为了驱动使用正确,附加的系统库是必需的(请参阅下面的安装部分)。
|
||||
|
||||
### 迁移自 UIAutomation 驱动
|
||||
|
||||
如果您正在从 Appium 的旧驱动程序:[UIAutomation-based driver](/docs/en/drivers/ios-uiautomation.md) 迁移到XCUITest驱动,您可能想知道这个:[迁移指南](/docs/en/advanced-concepts/migrating-to-xcuitest.md)。
|
||||
|
||||
|
||||
### 使用
|
||||
|
||||
在使用 XCUITest 驱动来创建一个会话链接时,需要包含 `automationName` [capability](#TODO) 参数在您的 [创建新会话请求](#TODO)中,且其值应该是:`XCUITest`。当然,您也至少传递这些必传参数:`platformName`, `platformVersion`, `deviceName` 与 `app`。
|
||||
|
||||
在 iPhone 与 iPad 设备上,参数 `platformName` 需要是 `iOS`。在 tvOS 设备上,参数 `platformName` 需要是 `tvOS`。
|
||||
|
||||
- iOS
|
||||
```json
|
||||
{
|
||||
"automationName": "XCUITest",
|
||||
"platformName": "iOS",
|
||||
"platformVersion": "12.2",
|
||||
"deviceName": "iPhone 8",
|
||||
...
|
||||
}
|
||||
```
|
||||
- tvOS
|
||||
```json
|
||||
{
|
||||
"automationName": "XCUITest",
|
||||
"platformName": "tvOS",
|
||||
"platformVersion": "12.2",
|
||||
"deviceName": "Apple TV",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### 功能参数
|
||||
|
||||
XCUITest 驱动支持许多通用的功能参数 [Appium
|
||||
capabilities](/docs/en/writing-running-appium/caps.md), 同时,有许多特有的功能参数,可以参考这篇文档查找这些功能参数:[appium-xcuitest-driver
|
||||
README](https://github.com/appium/appium-xcuitest-driver#desired-capabilities).
|
||||
|
||||
如果要在 Safari 上测试,而不是在您的 App 上测试的话,请设置 `app` 为空,并且设置 `browserName` 为 `Safari`。
|
||||
|
||||
|
||||
### 命令
|
||||
|
||||
如果需要查看 Appium 支持的命令,及它们是如何映射到 XCUITest 驱动的,可以参考:[API
|
||||
Reference](#TODO).
|
||||
|
||||
|
||||
### 基础设置
|
||||
|
||||
_(我们推荐使用 [Homebrew](https://brew.sh) 来安装系统依赖)_
|
||||
|
||||
1. 确保您拥有 Appium 的基础依赖(例如:Node 与 NPM)来安装和配置 Appium。
|
||||
|
||||
如果您的自动化测试不使用真机运行,那么您已经完成设置了! 使用模拟器运行一个自动化测试app, 参数 `app` 需要设置为 App 的绝对路径或下载地址,它需要是一个后缀为 `.app` 或 `.app.zip` 这样的,为模拟器构建的 App 文件。
|
||||
|
||||
### 真机设备设置
|
||||
|
||||
Automating a real device with XCUITest is considerably more complicated,
|
||||
在真机上使用 XCUITest 进行自动化要复杂得多,原因是
|
||||
苹果对在真实设备上运行应用程序的限制。请查看 [XCUITest real device setup doc](ios-xcuitest-real-devices.md) 获取介绍 。
|
||||
|
||||
一旦设置完成,在真机上运行会话可以使用这些功能:
|
||||
|
||||
* `app` 或 `bundleId` - 一个是:指定应用程序 (您使用的 `.ipa` 文件的本地路径或下载地址), 另一个是:如果您已经安装了 App, 用以标示您需要让 Appium 拉起哪一个应用。
|
||||
* `udid` - 指定要在哪个设备上运行测试。 如果只有一个设备的话,它可以设置为 `auto` , Appium 将会自动找到并使用该设备。
|
||||
|
||||
|
||||
### 可选设置
|
||||
|
||||
* 安装 idb 可以更好地处理各种iOS模拟器操作,
|
||||
例如:生物特征、地理位置设置和窗口聚焦。
|
||||
* 参考 https://github.com/appium/appium-idb#installation 以安装一些必要的库 (自 Appium 1.14.0 起 )
|
||||
|
||||
* 安装 [AppleSimulatorUtils](https://github.com/wix/AppleSimulatorUtils)
|
||||
以设置 [权限相关参数](https://github.com/appium/appium-xcuitest-driver#desired-capabilities)
|
||||
|
||||
### 运行测试生成的文件
|
||||
|
||||
在 iOS 上测试生成的文件有时会很大。 它包括:日志文件、临时文件以及Xcode运行时产生的数据。如果需要删除这些文件,一般来说,可以从以下地址找到它们:
|
||||
|
||||
```
|
||||
$HOME/Library/Logs/CoreSimulator/*
|
||||
$HOME/Library/Developer/Xcode/DerivedData/*
|
||||
```
|
||||
|
||||
### 键盘设置
|
||||
高于 Appium 1.14.0,Appium 默认配置键盘首选项以让测试运行地更加稳定。您可以通过这些 API 来设置。
|
||||
|
||||
- 可以在 _Keyboards_ 关闭 `Auto-Correction`
|
||||
- 可以在 _Keyboards_ 关闭 `Predictive`
|
||||
- Mark keyboard tutorial as complete
|
||||
- (仅模拟器生效) 打开虚拟键盘
|
||||
|
||||
### 可访问偏好设置调整
|
||||
|
||||
在某些情况下,启用以下首选项有助于使某些视图元素可访问。
|
||||
Appium不会自动修改这些设置,因为它们可能会影响被测试应用程序的执行方式。
|
||||
如果需要这些设置,需要您手动设置。
|
||||
|
||||
- 可以在 _Settings > Accessibility_ 打开 `Spoken Content`
|
||||
- 可以在 _Settings > Accessibility_ 打开 `Speak Selection`
|
||||
@@ -0,0 +1,41 @@
|
||||
# Appium version 1 End Of Life (EOL) Plan
|
||||
|
||||
Appium 1 has been around for quite a while and now is the time to move
|
||||
project development forward and to provide even better automation
|
||||
experience with Appium version 2.
|
||||
|
||||
## Upgrading to Appium 2.x
|
||||
|
||||
While Appium 2.0 leaves most of the fundamental automation behaviors from the various platfrom
|
||||
drivers untouched, it does rework the way that drivers are installed and accessed, and enforces
|
||||
stricter protocol usage, among other changes. For that reason, it's important to fully read,
|
||||
understand, and implement the steps discussed in the [Appium 1.x to 2.x Migration
|
||||
Guide](../advanced-concepts/migrating-to-appium-2.0.md). There you will find a full list of the
|
||||
changes, their rationale, and what you will need to do to keep your tests running well.
|
||||
|
||||
## Transition Plan
|
||||
|
||||
As of May 2020, Appium 2.0 has been in beta release and ready for your use and testing. Please
|
||||
begin the process of ensuring your test suites, or any other software built on Appium, is
|
||||
compatible with Appium 2.0.
|
||||
|
||||
The tentative release date of Appium 2.0 is *September 15, 2021*. After the release of
|
||||
Appium 2.0 (whether it is on that date or not), we will not be making any feature additions to the
|
||||
Appium 1.x server. We will however make the following kinds of updates for 6 months following the
|
||||
release of Appium 2.0 (until March 15, 2022):
|
||||
|
||||
* Feature updates and bug fixes to core drivers (XCUITest, UiAutomator2, Espresso). For the first
|
||||
6 months of Appium 2.x's life, the core drivers will be supported on both Appium 1.x and 2.x.
|
||||
* Bug fixes to the Appium 1.x server.
|
||||
|
||||
Once the 6 months of transition have passed, we will publish new versions of the core drivers that
|
||||
will only be compatible with Appium 2.x. From that point forward, no features, platform support, or
|
||||
patches will be added to 1.x.
|
||||
|
||||
Based on the current release schedule, it is likely that Appium 1.22.x will be the last in the set
|
||||
of Appium 1.x releases.
|
||||
|
||||
## Documentation
|
||||
|
||||
Since Appium 2.0's documentation may not be published on the website until its release, you may
|
||||
want to check out the `docs` folders in this repo, under the `2.0` git branch.
|
||||
@@ -0,0 +1,129 @@
|
||||
# Migrating to Appium 2.x from Appium 1.x
|
||||
|
||||
This document is a guide for those who are using Appium 1.x and wish to migrate to Appium 2.x. It contains a list of breaking changes and how to migrate your environments or test suites to ensure compatibility with Appium 2.0.
|
||||
|
||||
## Overview of Appium 2.0
|
||||
|
||||
Appium 2.0 is the most major new release of Appium in over 5 years. The changes in Appium 2.0 are *not* primarily related to changes in automation behaviors for specific platforms. Instead, Appium 2.0 reenvisions Appium as a *platform* where "drivers" (code projects that introduce support for automation of a given platform) and "plugins" (code projects that allow for overriding, altering, extending, or adding behaviors to Appium) can be easily created and shared.
|
||||
|
||||
At the same time, the Appium project is taking the opportunity to remove many old and deprecated bits of functionality.
|
||||
|
||||
Together these do introduce a few breaking changes to how Appium is installed, how drivers and various features are managed, and protocol support. These are detailed below.
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
Have a look at the [Appium 2.0 release notes](https://github.com/appium/appium/releases/tag/v2.0.0-beta) for a more comprehensive list of changes. Here we call out the breaking changes and what you need to do do account for them.
|
||||
|
||||
#### Installing drivers during setup
|
||||
|
||||
When you installed Appium 1.x, all available drivers would be installed at the same time as the main Appium server. This is no longer the case. Simply installing Appium 2.0 (e.g., by `npm install -g appium`), will install the Appium server only, but no drivers. To install drivers, you must instead use the new [Appium extension CLI](../drivers/driver-cli.md). For example, to install the latest versions of the XCUITest and UiAutomator2 drivers, after installing Appium you would run the following commands:
|
||||
|
||||
```
|
||||
appium driver install xcuitest
|
||||
appium driver install uiautomator2
|
||||
```
|
||||
|
||||
At this point, your drivers are installed and ready. There's a lot more you can do with this CLI so be sure to check out the docs on it.
|
||||
If you're running in a CI environment or want to install Appium along with some drivers all in one step, you can do so using some special flags during install, for example:
|
||||
|
||||
```
|
||||
npm install -g appium --drivers=xcuitest,uiautomator2
|
||||
```
|
||||
|
||||
This will install Appium and the two drivers for you in one go.
|
||||
|
||||
#### Driver updates
|
||||
|
||||
In the past, to get updates to your iOS or Android drivers, you'd simply wait for those updates to be rolled into a new release of Appium, and then update your Appium version. With Appium 2.x, the Appium server and the Appium drivers are versioned and released separately. This means that drivers can be on their own release cadence and that you can get driver updates as they happen, rather than waiting for a new Appium server release. The way to check for driver updates is with the CLI:
|
||||
|
||||
```
|
||||
appium driver list --updates
|
||||
```
|
||||
|
||||
If any updates are available, you can then run the `update` command for any given driver:
|
||||
|
||||
```
|
||||
appium driver update xcuitest
|
||||
```
|
||||
|
||||
To update the Appium server itself, you do the same thing as in the past: `npm install -g appium`. Now, installing new versions of the Appium server will leave your drivers intact, so the whole process will be much more quick.
|
||||
|
||||
#### Protocol changes
|
||||
|
||||
Appium's API is based on the [W3C WebDriver Protocol](https://www.w3.org/TR/webdriver/), and it has supported this protocol for years. Before the W3C WebDriver Protocol was designed as a web standard, several other protocols were used for both Selenium and Appium. These protocols were the "JSONWP" (JSON Wire Protocol) and "MSJONWP" (Mobile JSON Wire Protocol). The W3C Protocol differs from the (M)JSONWP protocols in a few small ways.
|
||||
|
||||
Up until Appium 2.0, Appium supported both protocols, so that older Selenium/Appium clients could still communicate with newer Appium servers. Moving forward, support for older protocols will be removed.
|
||||
|
||||
*Capabilities*
|
||||
|
||||
One significant difference between old and new protocols is in the format of capabilities. Previously called "desired capabilities", and now called simply "capabilities", there is now a requirement for a so-called "vendor prefix" on any non-standard capabilities. The list of standard capabilities is given in the [WebDriver Protocol spec](https://www.w3.org/TR/webdriver/#capabilities), and includes a few commonly used capabilities such as `browserName` and `platformName`.
|
||||
|
||||
These standard capabilities continue to be used as-is. All other capabilities must include a "vendor prefix" in their name. A vendor prefix is a string followed by a colon, such as `appium:`. Most of Appium's capabilities go beyond the standard W3C capabilities and must therefore include vendor prefixes (we recommend that you use `appium:` unless directed otherwise by documentation). For example:
|
||||
|
||||
* `appium:app`
|
||||
* `appium:noReset`
|
||||
* `appium:deviceName`
|
||||
|
||||
This requirement may or may not be a breaking change for your test suites when targeting Appium 2.0. If you're using an updated Appium client, the client will add the `appium:` prefix for you on all necessary capabilities. New versions of the Appium Inspector tool will also do this. Cloud-based Appium providers may also do this. So simply be aware that if you get any messages to the effect that your capabilities lack a vendor prefix, this is how you solve that problem.
|
||||
|
||||
On a related note, it will no longer be possible to start Appium sessions using WebDriver clients that don't support the W3C protocol (see below for a comment to this effect for the WD library).
|
||||
|
||||
*Removed Commands*
|
||||
|
||||
Commands which were a part of the old JSON Wire Protocol and not a part of the W3C Protocol are no longer available:
|
||||
|
||||
* TODO (these commands are being identified and removed and will be updated here when complete)
|
||||
|
||||
If you use a modern Appium or Selenium client, you should no longer have access to these anyway, so any breaking changes should appear on the client side first and foremost.
|
||||
|
||||
#### Image analysis features moved to plugin
|
||||
|
||||
One of the design goals for Appium 2.0 is to migrate non-core features into special extensions called [plugins](#TODO). This allows people to opt into features which require extra time to download or extra system setup. The various image-related features of Appium (image comparison, finding elements by image, etc...) have been moved into an officially supported plugin called [images](https://github.com/appium/appium-plugins/tree/master/packages/images).
|
||||
|
||||
If you use these image-related methods, to continue accessing them you will need to do two things.
|
||||
|
||||
1. Install the plugin: `appium plugin install images`
|
||||
2. Ensure you start the Appium server with access to run the plugin by including it in the list of plugins designated on the command line, e.g., `appium --plugins=images`
|
||||
|
||||
Image-related commands will also be removed on the client side of things, which means you will need to follow the instructions on the plugin README for installing client-side plugins to access these features.
|
||||
|
||||
#### Old drivers removed
|
||||
|
||||
The old iOS and Android (UiAutomator 1) drivers and related tools (e.g., `authorize-ios`) have been removed. They haven't been relevant for many years anyway.
|
||||
|
||||
#### Internal packages renamed
|
||||
|
||||
Some Appium-internal NPM packages have been renamed (for example, `appium-base-driver` is now `@appium/base-driver`). This is not a breaking change for Appium users, only for people who have built software that directly incorporates Appium's code.
|
||||
|
||||
#### "WD" JavaScript client library no longer supported
|
||||
|
||||
For many years, some of Appium's authors maintained the [WD](https://github.com/admc/wd) client library. This library has been deprecated and has not been updated for use with the W3C WebDriver protocol. As such, if you're using this library you'll need to move to a more modern one. We recommend [WebdriverIO](https://webdriver.io).
|
||||
|
||||
#### Appium Inspector split out from Appium Desktop
|
||||
|
||||
The inspecting portion of Appium Desktop has been moved to its own app, Appium Inspector: [github.com/appium/appium-inspector](https://github.com/appium/appium-inspector). It's fully compatible with Appium 2.0 servers. Simply download it and run it on its own. You no longer need the GUI Appium Desktop server to inspect apps. The Appium Desktop server will continue to be supported at its original site: [github.com/appium/appium-desktop](https://github.com/appium/appium-desktop). It will simply no longer bundle the Inspector with it.
|
||||
|
||||
You can also now use the Appium Inspector without downloading anything, by visiting the [web version of Appium Inspector](https://inspector.appiumpro.com). Note that to test against local servers, you'll need to start the server with `--allow-cors` so that the browser-based version of Appium Inspector can access your Appium server to start sessions.
|
||||
|
||||
## Major New Features
|
||||
|
||||
Apart from the breaking changes mentioned above, in this section is a list of some of the major new features you may wish to take advantage of with Appium 2.0.
|
||||
|
||||
#### Plugins
|
||||
|
||||
*Server Plugins*
|
||||
|
||||
TODO
|
||||
|
||||
*Client Plugins*
|
||||
|
||||
TODO
|
||||
|
||||
#### Install drivers and plugins from anywhere
|
||||
|
||||
TODO
|
||||
|
||||
#### Driver and Plugin CLI args
|
||||
|
||||
TODO
|
||||
|
||||
@@ -57,7 +57,12 @@ get_clipboard
|
||||
Get the content of the system clipboard
|
||||
|
||||
(For iOS real devices) Apple security preferences require the WebDriverAgentRunner application to be in [foreground](https://github.com/appium/WebDriverAgent/issues/330) in order to be able to receive the system clipboard content. Otherwise an empty string is always returned. Consider using [Activate App](/docs/en/commands/device/app/activate-app.md) and [Background App](/docs/en/commands/device/app/background-app.md) to change the foreground application.
|
||||
Activate App command cannot launch the WebDriverAgent properly on some environments. It depends on XCTest framework by Apple. Then, you should launch the WebDriverAgent using its Springboard's application icon. Follow the example below.
|
||||
|
||||
1. Open springboard, `@driver.activate_app 'com.apple.springboard'`.
|
||||
2. Find the WDA icon with `@driver.find_element :accessibility_id, 'WebDriverAgentRunner-Runner'` (It should be visible on the screen) and click it.
|
||||
3. Call the get clipboard API.
|
||||
4. Open the application under test with the [Activate App](/docs/en/commands/device/app/activate-app.md).
|
||||
|
||||
[//]: # (DO NOT EDIT THIS FILE! This is an auto-generated file. Editing for this document happens in /commands-yml/commands/device/clipboard/get-clipboard.yml)
|
||||
## Support
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[//]: # (DO NOT EDIT THIS FILE! This is an auto-generated file. Editing for this document happens in /commands-yml/commands/session/logs/get-log-types.yml)
|
||||
# Get available log types
|
||||
|
||||
Get the log for a given log type. Log buffer is reset after each request
|
||||
Get available log types as a list of strings
|
||||
[//]: # (DO NOT EDIT THIS FILE! This is an auto-generated file. Editing for this document happens in /commands-yml/commands/session/logs/get-log-types.yml)
|
||||
## Example Usage
|
||||
|
||||
|
||||
@@ -1,23 +1,70 @@
|
||||
# Releasing Appium
|
||||
Appium follows the GitLab flow approach. Releases are made on release branches that have the format `releases/x.y` where `x` is a major version and `y` is a minor version. The main Appium repository doesn't strictly follow semver (unlike the dependencies of Appium). A minor release (e.g.: `1.20.0`) means that we're creating a new release that has a newly created set of dependencies that are shrinkwrapped to the latest. A patch release (e.g.: `1.20.4`) isn't strictly a "patch", but just means that we're making an intermediary release that brings in small changes. For example, suppose I have a new version of `appium-xcuitest-driver` that I want to publish and it can't wait until the next minor release. To bring in that change, I would checkout the release branch (e.g.: `git checkout releases/1.20`); install the dependency (`npm install appium-xcuitest-driver@version && git commit -a -m 'bump appium-xcuitest-driver to version $version`); release a "release candidate" (see below) and then when it's ready, graduate the "release candidate" to general availability
|
||||
# Releasing an Appium Core Project
|
||||
|
||||
## Create a release branch
|
||||
1. `bash ./scripts/release-branch.sh x.y` where `x` is the major version and `y` is the minor version (example: `bash ./scripts/release-branch.sh 1.20`)
|
||||
* This will create:
|
||||
* A release branch `releases/1.20`
|
||||
* A git tag `v1.20.0-rc.0`
|
||||
* Publish a NPM package `1.20.0-rc.0` as a release candidate
|
||||
1. Create a changelog pull request
|
||||
This document explains how to release a new version of one or more packages in [this monorepo](https://github.com/appium/appium).
|
||||
|
||||
## Create a release candidate
|
||||
1. Checkout the release branch (e.g.: `git checkout releases/1.21 && git pull origin releases/1.21`)
|
||||
1. `bash ./scripts/release.sh rc`
|
||||
Note: the shell commands herein expect `lerna` in your `PATH`; you can also use `npx lerna`.
|
||||
|
||||
> See the [`lerna publish`](https://github.com/lerna/lerna/tree/main/commands/publish) docs for more info.
|
||||
|
||||
## Automatic Releases
|
||||
|
||||
This project uses [Lerna](https://lerna.js.org) to manage the versioning and publishing of its packages. Because we use [conventional commit messages](https://conventionalcommits.org), Lerna can parse these messages to determine which packages need releasing and with what version.
|
||||
|
||||
There's really only one step here. To version and publish _potentially all_ packages automatically, navigate to the branch to be published, and issue:
|
||||
|
||||
```bash
|
||||
lerna publish
|
||||
```
|
||||
|
||||
You will then see an interactive prompt, which lists the proposed new package versions to confirm. It will look a bit like this:
|
||||
|
||||
```bash
|
||||
lerna notice cli v4.0.0
|
||||
lerna info versioning independent
|
||||
lerna info Looking for changed packages since @appium/fake-driver@2.2.0
|
||||
lerna info ignoring diff in paths matching [ '**/test/**', '**/*.md' ]
|
||||
lerna info getChangelogConfig Successfully resolved preset "conventional-changelog-angular"
|
||||
|
||||
Changes:
|
||||
- appium: 2.0.0-beta.12 => 2.0.0-beta.13
|
||||
- @appium/base-driver: 8.0.0-beta.6 => 8.0.0-beta.7
|
||||
- @appium/doctor: 1.16.0 => 1.16.1
|
||||
- @appium/eslint-config-appium: 4.7.0 => 4.7.1
|
||||
- @appium/fake-driver: 2.2.0 => 2.2.1
|
||||
- @appium/gulp-plugins: 5.5.0 => 5.5.1
|
||||
- @appium/support: 2.53.0 => 2.53.1
|
||||
- @appium/test-support: 1.3.3 => 1.3.4
|
||||
|
||||
? Are you sure you want to publish these packages? (ynH)
|
||||
```
|
||||
|
||||
The information shown above:
|
||||
|
||||
1. The current version of the `lerna` executable
|
||||
1. A notice that this project uses "independent" versioning; i.e., all packages have their own version numbers.
|
||||
1. The _most recent Git tag_ Lerna found (`@appium/fake-driver@2.2.0`); if a changeset contains multiple tags, it only display one.
|
||||
1. Globs which are ignored when computing versions, e.g., a commit message beginning with `fix(scope)` will ignore any changesets consisting of _only_ modifications to files matching the globs--if any other files are modified, `fix(scope)` would cause a "patch" version bump, as per the conventional commits specification.
|
||||
1. Confirmation that Lerna is using conventional commit messages to determine versions
|
||||
1. A list of all packages that have changed and their new proposed versions
|
||||
|
||||
In this example, _every package_ has had changes since we last published. Lerna is also configured to use prerelease versions, where appropriate.
|
||||
|
||||
If you are happy with what it's going to do, confirm with `y` and continue to bump the package versions, then publish each. This does the same thing as running `npm version` then `npm publish` in each package, in series--all relevant lifecycle scripts are executed. In addition, any new tags will be pushed to GitHub, and releases will be created corresponding to the tags. The tags are of the format `[@scope/]<package-name>@<new-version>`.
|
||||
|
||||
> _TBD: update w/ Lerna's behavior around changelogs and GH releases is determined or further defined_
|
||||
|
||||
## Releasing a Single Package
|
||||
|
||||
**This is not recommended.** Lerna relies heavily on Git tags to do its thing, and a mistake would be costly. It is only _possible_ because we use independent versioning.
|
||||
|
||||
1. Navigate to a package directory and call `npm version --no-git-tag-version <newversion>`.
|
||||
1. You **must** tag the version manually using the format `[@scope/]<package-name>@<new-version>`.
|
||||
1. Push the new changeset and tag to GitHub.
|
||||
1. Issue `npm publish` from this package directory.
|
||||
1. You will need to edit `CHANGELOG.md` and create the GitHub release yourself, which sucks for you.
|
||||
|
||||
## Publish to GA
|
||||
1. Checkout the release branch (e.g.: `git checkout releases/1.21 && git pull origin releases/1.21`)
|
||||
1. `bash ./scripts/release.sh latest`
|
||||
1. Update the site docs by going to https://github.com/appium/appium.io/pulls and merging the latest pull request that was opened by the Triager bot. Close any other pull requests opened by Triager bot.
|
||||
1. Create a new release on GitHub: go to `https://github.com/appium/appium/releases/tag/v<VERSION>` and hit "Edit Tag". Make the release name `<VERSION>` (e.g., `2.0.5`), then paste in the changelog (but not the changelog header for this version). If it's a beta release, mark as pre-release.
|
||||
1. Create a new post on discuss.appium.io announcing the release. Post it in the "News" category. Paste in the changelog and any choice comments. Pin it and unpin the previous release post.
|
||||
1. Begin process of releasing `appium-desktop`.
|
||||
|
||||
1. Create a new post on <https://discuss.appium.io> announcing the release of the package(s). Post it in the "News" category. Paste in the changelog and any choice comments. Pin it and unpin the previous release post.
|
||||
1. Begin process of releasing [`appium-desktop`](https://github.com/appium/appium-desktop).
|
||||
1. Notify @jlipps to so he can tweet a link to the discuss post.
|
||||
|
||||
@@ -15,83 +15,28 @@ All flags are optional, but some are required in conjunction with certain others
|
||||
|----|-------|-----------|-------|
|
||||
|`--shell`|null|Enter REPL mode||
|
||||
|`--allow-cors`|false|Turn on CORS compatibility mode, which will allow connections to the Appium server from within websites hosted on any domain. Be careful when enabling this feature, since there is a potential security risk if you visit a website that uses a cross-domain request to initiate or introspect sessions on your running Appium server.||
|
||||
|`--ipa`|null|(IOS-only) abs path to compiled .ipa file|`--ipa /abs/path/to/my.ipa`|
|
||||
|`-a`, `--address`|0.0.0.0|IP Address to listen on|`--address 0.0.0.0`|
|
||||
|`-p`, `--port`|4723|port to listen on|`--port 4723`|
|
||||
|`-pa`, `--base-path`|null|Initial path segment where the Appium/WebDriver API will be hosted. Every endpoint will be behind this segment.|`--base-path /wd/hub`|
|
||||
|`-ca`, `--callback-address`|null|callback IP Address (default: same as --address)|`--callback-address 127.0.0.1`|
|
||||
|`-cp`, `--callback-port`|null|callback port (default: same as port)|`--callback-port 4723`|
|
||||
|`-bp`, `--bootstrap-port`|4724|(Android-only) port to use on device to talk to Appium|`--bootstrap-port 4724`|
|
||||
|`-r`, `--backend-retries`|3|(iOS-only) How many times to retry launching Instruments before saying it crashed or timed out|`--backend-retries 3`|
|
||||
|`--session-override`|false|Enables session override (clobbering)||
|
||||
|`-l`, `--pre-launch`|false|Pre-launch the application before allowing the first session (Requires --app and, for Android, --app-pkg and --app-activity)||
|
||||
|`-g`, `--log`|null|Also send log output to this file|`--log /path/to/appium.log`|
|
||||
|`--log-level`|debug|Set the server log level for console and logfile (specified as `console-level:logfile-level`, with both being the same if only one value is supplied). Possible values are `debug`, `info`, `warn`, `error`, which are progressively less verbose.|`--log-level error:debug`|
|
||||
|`--log-timestamp`|false|Show timestamps in console output||
|
||||
|`--local-timezone`|false|Use local timezone for timestamps||
|
||||
|`--log-no-colors`|false|Do not use colors in console output||
|
||||
|`-G`, `--webhook`|null|Also send log output to this HTTP listener|`--webhook localhost:9876`|
|
||||
|`--safari`|false|(IOS-Only) Use the safari app||
|
||||
|`--default-device`, `-dd`|false|(IOS-Simulator-only) use the default simulator that instruments launches on its own||
|
||||
|`--force-iphone`|false|(IOS-only) Use the iPhone Simulator no matter what the app wants||
|
||||
|`--force-ipad`|false|(IOS-only) Use the iPad Simulator no matter what the app wants||
|
||||
|`--tracetemplate`|null|(IOS-only) .tracetemplate file to use with Instruments|`--tracetemplate /Users/me/Automation.tracetemplate`|
|
||||
|`--instruments`|null|(IOS-only) path to instruments binary|`--instruments /path/to/instruments`|
|
||||
|`--nodeconfig`|null|Configuration JSON file to register appium with selenium grid|`--nodeconfig /abs/path/to/nodeconfig.json`|
|
||||
|`-ra`, `--robot-address`|0.0.0.0|IP Address of robot|`--robot-address 0.0.0.0`|
|
||||
|`-rp`, `--robot-port`|-1|port for robot|`--robot-port 4242`|
|
||||
|`--chromedriver-port`|9515|Port upon which ChromeDriver will run|`--chromedriver-port 9515`|
|
||||
|`--chromedriver-executable`|null|ChromeDriver executable full path||
|
||||
|
||||
|`--show-config`|false|Show info about the appium server configuration and exit||
|
||||
|`--no-perms-check`|false|Bypass Appium's checks to ensure we can read/write necessary files||
|
||||
|`--strict-caps`|false|Cause sessions to fail if desired caps are sent in that Appium does not recognize as valid for the selected device||
|
||||
|`--isolate-sim-device`|false|Xcode 6 has a bug on some platforms where a certain simulator can only be launched without error if all other simulator devices are first deleted. This option causes Appium to delete all devices other than the one being used by Appium. Note that this is a permanent deletion, and you are responsible for using simctl or xcode to manage the categories of devices used with Appium.||
|
||||
|`--tmp`|null|Absolute path to directory Appium can use to manage temporary files, like built-in iOS apps it needs to move around. On *nix/Mac defaults to /tmp, on Windows defaults to C:\Windows\Temp||
|
||||
|`--trace-dir`|null|Absolute path to directory Appium use to save ios instruments traces, defaults to <tmp dir>/appium-instruments||
|
||||
|`--debug-log-spacing`|false|Add exaggerated spacing in logs to help with visual inspection||
|
||||
|`--suppress-adb-kill-server`|false|(Android-only) If set, prevents Appium from killing the adb server instance||
|
||||
|`--async-trace`|false|Add long stack traces to log entries. Recommended for debugging only.||
|
||||
|`--webkit-debug-proxy-port`|27753|(IOS-only) Local port used for communication with ios-webkit-debug-proxy|`--webkit-debug-proxy-port 27753`|
|
||||
|
||||
|`-dc`, `--default-capabilities`|{}|Set the default desired capabilities, which will be set on each session unless overridden by received capabilities.|`--default-capabilities [ '{"app": "myapp.app", "deviceName": "iPhone Simulator"}' | /path/to/caps.json ]`|
|
||||
|`--reboot`|false| - (Android-only) reboot emulator after each session and kill it at the end||
|
||||
|`--command-timeout`|60|[DEPRECATED] No effect. This used to be the default command timeout for the server to use for all sessions (in seconds and should be less than 2147483). Use newCommandTimeout cap instead||
|
||||
|`-k`, `--keep-artifacts`|false|[DEPRECATED] - no effect, trace is now in tmp dir by default and is cleared before each run. Please also refer to the --trace-dir flag.||
|
||||
|`--platform-name`|null|[DEPRECATED] - Name of the mobile platform: iOS, Android, or FirefoxOS|`--platform-name iOS`|
|
||||
|`--platform-version`|null|[DEPRECATED] - Version of the mobile platform|`--platform-version 7.1`|
|
||||
|`--automation-name`|null|[DEPRECATED] - Name of the automation tool: Appium, XCUITest, etc.|`--automation-name Appium`|
|
||||
|`--device-name`|null|[DEPRECATED] - Name of the mobile device to use|`--device-name iPhone Retina (4-inch), Android Emulator`|
|
||||
|`--browser-name`|null|[DEPRECATED] - Name of the mobile browser: Safari or Chrome|`--browser-name Safari`|
|
||||
|`--app`|null|[DEPRECATED] - IOS: abs path to simulator-compiled .app file or the bundle_id of the desired target on device; Android: abs path to .apk file|`--app /abs/path/to/my.app`|
|
||||
|`-lt`, `--launch-timeout`|90000|[DEPRECATED] - (iOS-only) how long in ms to wait for Instruments to launch||
|
||||
|`--language`|null|[DEPRECATED] - Language for the iOS simulator / Android Emulator|`--language en`|
|
||||
|`--locale`|null|[DEPRECATED] - Locale for the iOS simulator / Android Emulator|`--locale en_US`|
|
||||
|`-U`, `--udid`|null|[DEPRECATED] - Unique device identifier of the connected physical device|`--udid 1adsf-sdfas-asdf-123sdf`|
|
||||
|`--orientation`|null|[DEPRECATED] - (IOS-only) use LANDSCAPE or PORTRAIT to initialize all requests to this orientation|`--orientation LANDSCAPE`|
|
||||
|`--no-reset`|false|[DEPRECATED] - Do not reset app state between sessions (IOS: do not delete app plist files; Android: do not uninstall app before new session)||
|
||||
|`--full-reset`|false|[DEPRECATED] - (iOS) Delete the entire simulator folder. (Android) Reset app state by uninstalling app instead of clearing app data. On Android, this will also remove the app after the session is complete.||
|
||||
|`--app-pkg`|null|[DEPRECATED] - (Android-only) Java package of the Android app you want to run (e.g., com.example.android.myApp)|`--app-pkg com.example.android.myApp`|
|
||||
|`--app-activity`|null|[DEPRECATED] - (Android-only) Activity name for the Android activity you want to launch from your package (e.g., MainActivity)|`--app-activity MainActivity`|
|
||||
|`--app-wait-package`|false|[DEPRECATED] - (Android-only) Package name for the Android activity you want to wait for (e.g., com.example.android.myApp)|`--app-wait-package com.example.android.myApp`|
|
||||
|`--app-wait-activity`|false|[DEPRECATED] - (Android-only) Activity name for the Android activity you want to wait for (e.g., SplashActivity)|`--app-wait-activity SplashActivity`|
|
||||
|`--device-ready-timeout`|5|[DEPRECATED] - (Android-only) Timeout in seconds while waiting for device to become ready|`--device-ready-timeout 5`|
|
||||
|`--android-coverage`|false|[DEPRECATED] - (Android-only) Fully qualified instrumentation class. Passed to -w in adb shell am instrument -e coverage true -w |`--android-coverage com.my.Pkg/com.my.Pkg.instrumentation.MyInstrumentation`|
|
||||
|`--avd`|null|[DEPRECATED] - (Android-only) Name of the avd to launch|`--avd @default`|
|
||||
|`--avd-args`|null|[DEPRECATED] - (Android-only) Additional emulator arguments to launch the avd|`--avd-args -no-snapshot-load`|
|
||||
|`--use-keystore`|false|[DEPRECATED] - (Android-only) When set the keystore will be used to sign apks.||
|
||||
|`--keystore-path`|<user>/.android/debug.keystore|[DEPRECATED] - (Android-only) Path to keystore||
|
||||
|`--keystore-password`|android|[DEPRECATED] - (Android-only) Password to keystore||
|
||||
|`--key-alias`|androiddebugkey|[DEPRECATED] - (Android-only) Key alias||
|
||||
|`--key-password`|android|[DEPRECATED] - (Android-only) Key password||
|
||||
|`--intent-action`|android.intent.action.MAIN|[DEPRECATED] - (Android-only) Intent action which will be used to start activity|`--intent-action android.intent.action.MAIN`|
|
||||
|`--intent-category`|android.intent.category.LAUNCHER|[DEPRECATED] - (Android-only) Intent category which will be used to start activity|`--intent-category android.intent.category.APP_CONTACTS`|
|
||||
|`--intent-flags`|0x10200000|[DEPRECATED] - (Android-only) Flags that will be used to start activity|`--intent-flags 0x10200000`|
|
||||
|`--intent-args`|null|[DEPRECATED] - (Android-only) Additional intent arguments that will be used to start activity|`--intent-args 0x10200000`|
|
||||
|`--dont-stop-app-on-reset`|false|[DEPRECATED] - (Android-only) When included, refrains from stopping the app before restart||
|
||||
|`--calendar-format`|null|[DEPRECATED] - (IOS-only) calendar format for the iOS simulator|`--calendar-format gregorian`|
|
||||
|`--native-instruments-lib`|false|[DEPRECATED] - (IOS-only) IOS has a weird built-in unavoidable delay. We patch this in appium. If you do not want it patched, pass in this flag.||
|
||||
|`--keep-keychains`|false|[DEPRECATED] - (iOS-only) Whether to keep keychains (Library/Keychains) when reset app between sessions||
|
||||
|`--localizable-strings-dir`|en.lproj|[DEPRECATED] - (IOS-only) the relative path of the dir where Localizable.strings file resides |`--localizable-strings-dir en.lproj`|
|
||||
|`--show-ios-log`|false|[DEPRECATED] - (IOS-only) if set, the iOS system log will be written to the console||
|
||||
|`--relaxed-security`|false|Disable additional security checks, so it is possible to use some advanced features, provided by drivers supporting this option. Only enable it if all the clients are in the trusted network and it is not the case if a client could potentially break out of the session sandbox. Can override enabling of specific features with --deny-insecure. See also the [security doc](/docs/en/writing-running-appium/security.md)||
|
||||
|`--allow-insecure`|[]|Allow a list of features which are considered insecure and must be turned on explicitly by system administrators. Feature names are documented by the relevant server/driver. Should be a comma-separated list, or a path to a filename containing one feature name per line. Features listed in --deny-insecure will override anything listed here. Does not make sense to use in conjunction with --relaxed-security. See also the [security doc](/docs/en/writing-running-appium/security.md)|`--allow-insecure=foo,bar`|
|
||||
|`--deny-insecure`|[]|Specify a list of features which will never be allowed to run, even if --relaxed-security is turned on, and even if feature names are listed with --allow-insecure. Should be a comma-separated list, or a path to a filename containing one feature name per line. See also the [security doc](/docs/en/writing-running-appium/security.md)|`--deny-insecure=foo,bar`|
|
||||
|
||||
@@ -7,6 +7,7 @@ module.exports = {
|
||||
['Getting Started', 'getting-started.md'],
|
||||
['Supported Platforms', 'platform-support.md'],
|
||||
['API Documentation', 'api.md'],
|
||||
['Appium 1.x EOL Plan', '1.x-eol.md'],
|
||||
]],
|
||||
['Drivers', ['drivers',
|
||||
['Driver CLI', 'driver-cli.md'],
|
||||
@@ -300,6 +301,7 @@ module.exports = {
|
||||
]],
|
||||
]],
|
||||
['Advanced', ['advanced-concepts',
|
||||
['Migrating to Appium 2.x', 'migrating-to-appium-2.0.md'],
|
||||
['Finding Image Elements', 'image-elements.md'],
|
||||
['Using Element Finding Plugins', 'element-finding-plugins.md'],
|
||||
['Migrating to XCUITest', 'migrating-to-xcuitest.md'],
|
||||
@@ -330,17 +332,18 @@ module.exports = {
|
||||
['已支持的平台', 'platform-support.md'],
|
||||
// ['API 文档', 'api.md'],
|
||||
]],
|
||||
/*['驱动程序', ['drivers',
|
||||
['驱动程序', ['drivers',
|
||||
// ['Driver CLI', 'driver-cli.md'],
|
||||
['XCUITest (iOS)', 'ios-xcuitest.md'],
|
||||
['XCUITest Real Devices (iOS)', 'ios-xcuitest-real-devices.md'],
|
||||
['UIAutomation (iOS)', 'ios-uiautomation.md'],
|
||||
['UIAutomation Safari Launcher (iOS)', 'ios-uiautomation-safari-launcher.md'],
|
||||
['UIAutomator (Android)', 'android-uiautomator.md'],
|
||||
// ['UIAutomation (iOS)', 'ios-uiautomation.md'],
|
||||
// ['UIAutomation Safari Launcher (iOS)', 'ios-uiautomation-safari-launcher.md'],
|
||||
// ['UIAutomator (Android)', 'android-uiautomator.md'],
|
||||
['UIAutomator2 (Android)', 'android-uiautomator2.md'],
|
||||
['Espresso (Android)', 'android-espresso.md'],
|
||||
['Windows', 'windows.md'],
|
||||
['Mac', 'mac.md'],
|
||||
]],*/
|
||||
// ['Espresso (Android)', 'android-espresso.md'],
|
||||
// ['Windows', 'windows.md'],
|
||||
// ['Mac', 'mac.md'],
|
||||
]],
|
||||
['编写 & 运行测试', ['writing-running-appium',
|
||||
['运行测试', 'running-tests.md'],
|
||||
['预期功能', 'caps.md'],
|
||||
|
||||
@@ -1,30 +1,23 @@
|
||||
{
|
||||
"ci": false,
|
||||
"command": {
|
||||
"exec": {
|
||||
"ignore": "@appium/eslint-config-appium"
|
||||
},
|
||||
"version": {
|
||||
"conventionalCommits": true,
|
||||
"createRelease": "github"
|
||||
}
|
||||
},
|
||||
"ignoreChanges": [
|
||||
"**/test/**",
|
||||
"**/*.md"
|
||||
],
|
||||
"npmClientArgs": [
|
||||
"--no-package-lock"
|
||||
],
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "2.0.0-beta.1",
|
||||
"command": {
|
||||
"bootstrap": {
|
||||
"npmClientArgs": [
|
||||
"--no-package-lock"
|
||||
],
|
||||
"ci": false
|
||||
},
|
||||
"exec": {
|
||||
"ignore": "@appium/eslint-config-appium"
|
||||
}
|
||||
},
|
||||
"changelog": {
|
||||
"repo": "appium/appium",
|
||||
"cacheDir": ".changelog",
|
||||
"labels": {
|
||||
"PR: Spec Compliancy :eyeglasses:": ":eyeglasses: Spec Compliancy",
|
||||
"PR: Breaking Change :boom:": ":boom: Breaking Change",
|
||||
"PR: New Feature :rocket:": ":rocket: New Feature",
|
||||
"PR: Bug Fix :bug:": ":bug: Bug Fix",
|
||||
"PR: Polish :nail_care:": ":nail_care: Polish",
|
||||
"PR: Docs :memo:": ":memo: Documentation",
|
||||
"PR: Internal :house:": ":house: Internal"
|
||||
}
|
||||
}
|
||||
"version": "independent"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "Automation for Apps.",
|
||||
"keywords": [
|
||||
"automation",
|
||||
"javascript",
|
||||
"selenium",
|
||||
"webdriver",
|
||||
"ios",
|
||||
"android",
|
||||
"firefoxos",
|
||||
"testing"
|
||||
],
|
||||
"homepage": "https://appium.io",
|
||||
"bugs": {
|
||||
"url": "https://github.com/appium/appium/issues"
|
||||
},
|
||||
@@ -14,7 +25,7 @@
|
||||
"author": "https://github.com/appium",
|
||||
"scripts": {
|
||||
"build": "lerna exec gulp transpile",
|
||||
"clean": "lerna clean -y && rm -rf node_modules && rm -f package-lock.json && rm -rf packages/*/build",
|
||||
"clean": "lerna clean -y && npx rimraf node_modules package-lock.json \"packages/*/build\"",
|
||||
"coverage": "lerna exec gulp coveralls",
|
||||
"doctor": "node ./packages/doctor",
|
||||
"e2e-test": "lerna exec gulp e2e-test",
|
||||
@@ -27,6 +38,10 @@
|
||||
"precommit-msg": "echo 'Pre-commit checks...' && exit 0",
|
||||
"reinstall": "npm run clean && npm install",
|
||||
"start": "node ./packages/appium",
|
||||
"sync-pkgs": "npm run sync-pkgs:appium-readme && npm run sync-pkgs:common-fields && npm run sync-pkgs:keywords",
|
||||
"sync-pkgs:appium-readme": "sync-monorepo-packages --force --no-package-json --packages=packages/appium README.md",
|
||||
"sync-pkgs:common-fields": "sync-monorepo-packages --fields=author,license,repository,bugs,homepage,engines",
|
||||
"sync-pkgs:keywords": "sync-monorepo-packages --field=keywords --packages='packages/*' --packages='!packages/eslint-config-appium'",
|
||||
"test": "lerna exec gulp once",
|
||||
"test:appium": "lerna exec --scope=appium gulp once",
|
||||
"test:base-driver": "lerna exec --scope=@appium/base-driver gulp once",
|
||||
@@ -40,6 +55,7 @@
|
||||
"test:gulp-plugins": "lerna exec --scope=@appium/gulp-plugins gulp once",
|
||||
"test:support": "lerna exec --scope=@appium/support gulp once",
|
||||
"test:test-support": "lerna exec --scope=@appium/test-support gulp once",
|
||||
"preversion": "npm run sync-pkgs && git add -A packages/appium/README.md packages/*/package.json",
|
||||
"watch": "lerna exec watch"
|
||||
},
|
||||
"pre-commit": [
|
||||
@@ -51,48 +67,52 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/eslint-config-appium": "file:./packages/eslint-config-appium",
|
||||
"@appium/fake-plugin": "^1.0.0",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/eslint-parser": "^7.14.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
|
||||
"@babel/plugin-proposal-numeric-separator": "^7.0.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
|
||||
"@babel/plugin-proposal-private-methods": "^7.0.0",
|
||||
"@babel/plugin-syntax-bigint": "^7.0.0",
|
||||
"@babel/plugin-transform-runtime": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/register": "^7.0.0",
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"appium-base-driver": "^7.6.0",
|
||||
"@appium/fake-plugin": "^1.2.0",
|
||||
"@babel/core": "^7.15.0",
|
||||
"@babel/eslint-parser": "^7.15.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5",
|
||||
"@babel/plugin-proposal-numeric-separator": "^7.14.5",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.14.5",
|
||||
"@babel/plugin-proposal-private-methods": "^7.14.5",
|
||||
"@babel/plugin-syntax-bigint": "^7.8.3",
|
||||
"@babel/plugin-transform-runtime": "^7.15.0",
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"@babel/register": "^7.15.3",
|
||||
"@babel/runtime": "^7.15.3",
|
||||
"appium-base-driver": "^7.9.0",
|
||||
"asyncbox": "^2.8.0",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-plugin-istanbul": "^6.0.0",
|
||||
"babel-plugin-source-map-support": "^2.0.1",
|
||||
"chai": "4.x",
|
||||
"chai-as-promised": "7.x",
|
||||
"chai-webdriverio-async": "^2.6.0",
|
||||
"colors": "^1.4.0",
|
||||
"eslint-find-rules": "^3.6.1",
|
||||
"fancy-log": "^1.3.2",
|
||||
"finalhandler": "^1.1.1",
|
||||
"fancy-log": "^1.3.3",
|
||||
"finalhandler": "^1.1.2",
|
||||
"get-port": "^5.1.1",
|
||||
"gulp": "^4.0.0",
|
||||
"handlebars": "^4.2.0",
|
||||
"gulp": "^4.0.2",
|
||||
"handlebars": "^4.7.7",
|
||||
"lerna": "^4.0.0",
|
||||
"lint-staged": "^11.0.0",
|
||||
"mjpeg-server": "^0.3.0",
|
||||
"mocha": "^8.0.1",
|
||||
"lint-staged": "^11.1.2",
|
||||
"mjpeg-server": "^0.3.1",
|
||||
"mocha": "9.0.1",
|
||||
"pre-commit": "1.x",
|
||||
"querystring": "^0.2.0",
|
||||
"serve-static": "^1.13.1",
|
||||
"sinon": "^9.0.0",
|
||||
"querystring": "^0.2.1",
|
||||
"serve-static": "^1.14.1",
|
||||
"sinon": "11.1.1",
|
||||
"sync-monorepo-packages": "^0.3.4",
|
||||
"validate.js": "^0.13.0",
|
||||
"wd": "^1.14.0",
|
||||
"webdriverio": "^6.12.0",
|
||||
"yaml-js": "^0.2.0"
|
||||
"webdriverio": "^6.12.1",
|
||||
"yaml-js": "^0.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-beta.13](https://github.com/appium/appium/compare/appium@2.0.0-beta.12...appium@2.0.0-beta.13) (2021-08-16)
|
||||
|
||||
|
||||
|
||||
# 2.0.0-beta (2021-08-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **appium:** load plugins before main server to allow plugin monkeypatching ([#15441](https://github.com/appium/appium/issues/15441)) ([52f4fc6](https://github.com/appium/appium/commit/52f4fc6a2cbdb612888ab2a23a704be32594a006))
|
||||
* **appium:** update references to @appium/support ([9295d0c](https://github.com/appium/appium/commit/9295d0c68284443ecd73add1f8fe0ff7910a7d7d))
|
||||
* **appium:** use proper base driver package & random ports ([e14d4fb](https://github.com/appium/appium/commit/e14d4fbd0fce16f208569b0cb9149b6307f78a5f))
|
||||
* **appium:** when updating extensions, make sure to actually remove the previous one first to avoid npm reinstall issues ([4839419](https://github.com/appium/appium/commit/483941974881bf2ad362c6e6cc35883743abcd2a))
|
||||
* **appium:** when updating extensions, package fields should also be updated in extensions.yaml ([756688b](https://github.com/appium/appium/commit/756688bddb624a6e9a5b5b6403db76eac65774f3))
|
||||
* do not assume git root and package.json have the same parent dir ([3c5fba7](https://github.com/appium/appium/commit/3c5fba7b38e02f4216d3a26340948d070f7ea9d9))
|
||||
* doc generation ([9e33c7a](https://github.com/appium/appium/commit/9e33c7ac1135306c6f0d1ff83b8076aecb54c554))
|
||||
* e2e-tests ([2499b49](https://github.com/appium/appium/commit/2499b49936660280eefaeb26cb3e4e9f82e36c20))
|
||||
* use random test port ([935b1f8](https://github.com/appium/appium/commit/935b1f80a47e89ccdf81781f35be5123bf8673d5))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add "run" command to the client interface in order to allow running driver-defined scripts ([#15356](https://github.com/appium/appium/issues/15356)) ([a265476](https://github.com/appium/appium/commit/a2654762b6a9156380bcdf53df4cb0a8deb061fa))
|
||||
* **appium:** Add driver and plugin server arg injection feature ([#15388](https://github.com/appium/appium/issues/15388)) ([d3c11e3](https://github.com/appium/appium/commit/d3c11e364dffff87ac38ac8dc3ad65a1e4534a9a))
|
||||
@@ -1,5 +1,121 @@
|
||||
# appium
|
||||
## Appium
|
||||
|
||||
## TODO
|
||||
[](https://npmjs.org/package/appium)
|
||||
[](https://david-dm.org/appium/appium)
|
||||
[](https://david-dm.org/appium/appium#info=devDependencies)
|
||||
|
||||
- Either add a specific `README.md` here or copy it from the root as a prepublish step.
|
||||
[](https://npmjs.org/package/appium)
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fappium%2Fappium?ref=badge_shield)
|
||||
|
||||
Appium is an open-source, cross-platform test automation tool for native,
|
||||
hybrid, and mobile web and desktop apps. We support simulators (iOS), emulators
|
||||
(Android), and real devices (iOS, Android, Windows, Mac).
|
||||
|
||||
Want to skip straight to the action? Check out our [getting
|
||||
started](/docs/en/about-appium/getting-started.md) doc.
|
||||
|
||||
### Supported Platforms
|
||||
|
||||
Appium supports app automation across a variety of platforms, like iOS,
|
||||
Android, and Windows. Each platform is supported by one or more "drivers",
|
||||
which know how to automate that particular platform. Choose a driver below for
|
||||
specific information about how that driver works and how to set it up:
|
||||
|
||||
* iOS
|
||||
* The [XCUITest Driver](/docs/en/drivers/ios-xcuitest.md)
|
||||
* (DEPRECATED) The [UIAutomation Driver](/docs/en/drivers/ios-uiautomation.md)
|
||||
* Android
|
||||
* The [Espresso Driver](/docs/en/drivers/android-espresso.md)
|
||||
* The [UiAutomator2 Driver](/docs/en/drivers/android-uiautomator2.md)
|
||||
* (DEPRECATED) The [UiAutomator Driver](/docs/en/drivers/android-uiautomator.md)
|
||||
* The [Windows Driver](/docs/en/drivers/windows.md) (for Windows Desktop apps)
|
||||
* The [Mac Driver](/docs/en/drivers/mac.md) (for Mac Desktop apps)
|
||||
|
||||
### Why Appium?
|
||||
|
||||
1. You don't have to recompile your app or modify it in any way, due
|
||||
to the use of standard automation APIs on all platforms.
|
||||
2. You can write tests with your favorite dev tools using any
|
||||
[WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html)-compatible
|
||||
language such as Java, Objective-C, JavaScript (Node), PHP, Python, Ruby,
|
||||
C#, Clojure, or Perl with the Selenium WebDriver API and [language-specific
|
||||
client libraries](/docs/en/about-appium/appium-clients.md).
|
||||
3. You can use any testing framework.
|
||||
4. Appium has built-in mobile web and hybrid app support. Within the same
|
||||
script, you can switch seamlessly between native app automation and webview
|
||||
automation, all using the WebDriver model that's already the standard for
|
||||
web automation.
|
||||
|
||||
Investing in the
|
||||
[WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html) protocol means
|
||||
you are betting on a single, free, and open protocol for testing that has become
|
||||
a web standard. Don't lock yourself into a proprietary stack.
|
||||
|
||||
For example, if you use Apple's XCUITest library without Appium you can only
|
||||
write tests using Obj-C/Swift, and you can only run tests through Xcode.
|
||||
Similarly, with Google's UiAutomator or Espresso, you can only write tests in
|
||||
Java/Kotlin. Appium opens up the possibility of true cross-platform native app
|
||||
automation, for mobile and beyond. Finally!
|
||||
|
||||
If you're new to Appium or want a more comprehensive description of what this is all
|
||||
about, please read our [Introduction to Appium
|
||||
Concepts](/docs/en/about-appium/intro.md).
|
||||
|
||||
### Requirements
|
||||
|
||||
Your environment needs to be set up for the particular platforms that you want
|
||||
to run tests on. Each of the drivers above documents the requirements for their
|
||||
particular brand of automation. At a minimum, you will need to be able to run
|
||||
Node.js 10+.
|
||||
|
||||
### Get Started
|
||||
|
||||
Check out our [Getting Started](/docs/en/about-appium/getting-started.md) guide
|
||||
to get going with Appium.
|
||||
|
||||
There is also a sample code that contains [many examples of tests in a variety
|
||||
of different languages](https://github.com/appium/appium/tree/master/sample-code)!
|
||||
|
||||
### Documentation
|
||||
|
||||
For prettily-rendered docs, please visit [appium.io](http://appium.io). You can
|
||||
always find the full list of Appium doc pages at [Appium's GitHub
|
||||
Repo](https://github.com/appium/appium/tree/master/docs/en/) as well.
|
||||
|
||||
[update-appium-io.yml](https://github.com/appium/appium/blob/master/ci-jobs/update-appium-io.yml) creates a PR
|
||||
by [CI job](https://dev.azure.com/AppiumCI/Appium%20CI/_build?definitionId=37).
|
||||
in the appium.io repository with the documentation update.
|
||||
|
||||
Once the PR has been merged, the latest documentation will be in [appium.io](http://appium.io)
|
||||
|
||||
### Contributing
|
||||
|
||||
Please take a look at our [contribution documentation](CONTRIBUTING.md)
|
||||
for instructions on how to build, test, and run Appium from the source.
|
||||
|
||||
### Roadmap
|
||||
|
||||
Interested in where Appium is heading in the future? Check out the [Roadmap](ROADMAP.md)
|
||||
|
||||
### Project History, Credits & Inspiration
|
||||
|
||||
* [History](http://appium.io/history)
|
||||
* [Credits](/docs/en/contributing-to-appium/credits.md)
|
||||
|
||||
### User Forums
|
||||
|
||||
Announcements and debates often take place on the [Discussion
|
||||
Group](https://discuss.appium.io), be sure to sign up!
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
We put together a [troubleshooting
|
||||
guide](/docs/en/writing-running-appium/other/troubleshooting.md). Please have a look
|
||||
here first if you run into any problems. It contains instructions for checking
|
||||
a lot of common errors and how to get in touch with the community if you're
|
||||
stumped.
|
||||
|
||||
### License
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fappium%2Fappium?ref=badge_large)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const _ = require('lodash');
|
||||
const { asyncify } = require('asyncbox');
|
||||
const { fs, logger } = require('appium-support');
|
||||
const { fs, logger } = require('@appium/support');
|
||||
const path = require('path');
|
||||
|
||||
const log = new logger.getLogger('ShrinkwrapValidator');
|
||||
|
||||
@@ -8,6 +8,12 @@ description:
|
||||
|
||||
(For iOS real devices)
|
||||
Apple security preferences require the WebDriverAgentRunner application to be in [foreground](https://github.com/appium/WebDriverAgent/issues/330) in order to be able to receive the system clipboard content. Otherwise an empty string is always returned. Consider using [Activate App](/docs/en/commands/device/app/activate-app.md) and [Background App](/docs/en/commands/device/app/background-app.md) to change the foreground application.
|
||||
Activate App command cannot launch the WebDriverAgent properly on some environments. It depends on XCTest framework by Apple. Then, you should launch the WebDriverAgent using its Springboard's application icon. Follow the example below.
|
||||
|
||||
1. Open springboard, `@driver.activate_app 'com.apple.springboard'`.
|
||||
2. Find the WDA icon with `@driver.find_element :accessibility_id, 'WebDriverAgentRunner-Runner'` (It should be visible on the screen) and click it.
|
||||
3. Call the get clipboard API.
|
||||
4. Open the application under test with the [Activate App](/docs/en/commands/device/app/activate-app.md).
|
||||
|
||||
example_usage:
|
||||
java:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: Get available log types
|
||||
short_description: Get the log for a given log type. Log buffer is reset after each request
|
||||
short_description: Get available log types as a list of strings
|
||||
|
||||
example_usage:
|
||||
java:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from 'path';
|
||||
import yaml from 'yaml-js';
|
||||
import { fs, mkdirp, util } from 'appium-support';
|
||||
import { fs, mkdirp, util } from '@appium/support';
|
||||
import validate from 'validate.js';
|
||||
import Handlebars from 'handlebars';
|
||||
import _ from 'lodash';
|
||||
|
||||
@@ -12,6 +12,8 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const log = require('fancy-log');
|
||||
|
||||
gulp.task('copy-fixtures', () => gulp.src('./test/fixtures/*').pipe(gulp.dest('./build/test/fixtures/')));
|
||||
|
||||
// remove 'fsevents' from shrinkwrap, since it causes errors on non-Mac hosts
|
||||
// see https://github.com/npm/npm/issues/2679
|
||||
|
||||
@@ -46,7 +48,8 @@ boilerplate({
|
||||
test: {
|
||||
files: ['${testDir}/**/*-specs.js']
|
||||
},
|
||||
testTimeout: 160000
|
||||
testTimeout: 160000,
|
||||
postTranspile: ['copy-fixtures']
|
||||
});
|
||||
|
||||
// generates server arguments readme
|
||||
|
||||
@@ -5,8 +5,8 @@ import { findMatchingDriver } from './drivers';
|
||||
import { BaseDriver, errors, isSessionCommand } from '@appium/base-driver';
|
||||
import B from 'bluebird';
|
||||
import AsyncLock from 'async-lock';
|
||||
import { parseCapsForInnerDriver, pullSettings } from './utils';
|
||||
import { util } from 'appium-support';
|
||||
import { parseExtensionArgs, parseCapsForInnerDriver, pullSettings } from './utils';
|
||||
import { util } from '@appium/support';
|
||||
|
||||
const desiredCapabilityConstraints = {
|
||||
automationName: {
|
||||
@@ -27,7 +27,7 @@ class AppiumDriver extends BaseDriver {
|
||||
// It is necessary to set `--tmp` here since it should be set to
|
||||
// process.env.APPIUM_TMP_DIR once at an initial point in the Appium lifecycle.
|
||||
// The process argument will be referenced by BaseDriver.
|
||||
// Please call appium-support.tempDir module to apply this benefit.
|
||||
// Please call @appium/support.tempDir module to apply this benefit.
|
||||
if (args.tmpDir) {
|
||||
process.env.APPIUM_TMP_DIR = args.tmpDir;
|
||||
}
|
||||
@@ -155,7 +155,8 @@ class AppiumDriver extends BaseDriver {
|
||||
|
||||
const {
|
||||
driver: InnerDriver,
|
||||
version: driverVersion
|
||||
version: driverVersion,
|
||||
driverName
|
||||
} = this._findMatchingDriver(this.driverConfig, desiredCaps);
|
||||
this.printNewSessionAnnouncement(InnerDriver.name, driverVersion, InnerDriver.baseVersion);
|
||||
|
||||
@@ -164,8 +165,9 @@ class AppiumDriver extends BaseDriver {
|
||||
}
|
||||
|
||||
let runningDriversData, otherPendingDriversData;
|
||||
const d = new InnerDriver(this.args);
|
||||
const parsedInnerDriverArgs = parseExtensionArgs(this.args.driverArgs, driverName);
|
||||
|
||||
const d = new InnerDriver(this.args, true, parsedInnerDriverArgs);
|
||||
// We want to assign security values directly on the driver. The driver
|
||||
// should not read security values from `this.opts` because those values
|
||||
// could have been set by a malicious user via capabilities, whereas we
|
||||
@@ -405,7 +407,10 @@ class AppiumDriver extends BaseDriver {
|
||||
sessionId = _.truncate(sessionId, {length: 11});
|
||||
return this.pluginClasses.map((PluginClass) => {
|
||||
const name = `${PluginClass.pluginName} (${sessionId})`;
|
||||
return new PluginClass(name);
|
||||
const opts = {
|
||||
pluginArgs: parseExtensionArgs(this.args.pluginArgs, PluginClass.pluginName),
|
||||
};
|
||||
return new PluginClass(name, opts);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DEFAULT_BASE_PATH } from '@appium/base-driver';
|
||||
import {
|
||||
parseSecurityFeatures, parseDefaultCaps,
|
||||
parseSecurityFeatures, parseJsonStringOrFile,
|
||||
parsePluginNames, parseInstallTypes, parseDriverNames
|
||||
} from './parser-helpers';
|
||||
import {
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
DRIVER_TYPE, PLUGIN_TYPE
|
||||
} from '../extension-config';
|
||||
import {
|
||||
DEFAULT_CAPS_ARG, StoreDeprecatedDefaultCapabilityAction
|
||||
DEFAULT_CAPS_ARG
|
||||
} from './argparse-actions';
|
||||
|
||||
const DRIVER_EXAMPLE = 'xcuitest';
|
||||
@@ -69,13 +69,6 @@ const serverArgs = [
|
||||
dest: 'allowCors',
|
||||
}],
|
||||
|
||||
[['--reboot'], {
|
||||
default: false,
|
||||
dest: 'reboot',
|
||||
action: StoreDeprecatedDefaultCapabilityAction,
|
||||
required: false,
|
||||
help: '(Android-only) reboot emulator after each session and kill it at the end',
|
||||
}],
|
||||
|
||||
[['-a', '--address'], {
|
||||
default: '0.0.0.0',
|
||||
@@ -190,23 +183,6 @@ const serverArgs = [
|
||||
help: 'Configuration JSON file to register appium with selenium grid',
|
||||
}],
|
||||
|
||||
[['--chromedriver-port'], {
|
||||
default: null,
|
||||
dest: 'chromeDriverPort',
|
||||
required: false,
|
||||
action: StoreDeprecatedDefaultCapabilityAction,
|
||||
type: 'int',
|
||||
help: 'Port upon which ChromeDriver will run. If not given, Android driver will pick a random available port.',
|
||||
}],
|
||||
|
||||
[['--chromedriver-executable'], {
|
||||
default: null,
|
||||
dest: 'chromedriverExecutable',
|
||||
action: StoreDeprecatedDefaultCapabilityAction,
|
||||
required: false,
|
||||
help: 'ChromeDriver executable full path',
|
||||
}],
|
||||
|
||||
[['--show-config'], {
|
||||
default: false,
|
||||
dest: 'showConfig',
|
||||
@@ -257,13 +233,6 @@ const serverArgs = [
|
||||
help: 'Add exaggerated spacing in logs to help with visual inspection',
|
||||
}],
|
||||
|
||||
[['--suppress-adb-kill-server'], {
|
||||
dest: 'suppressKillServer',
|
||||
default: false,
|
||||
action: 'store_true',
|
||||
required: false,
|
||||
help: '(Android-only) If set, prevents Appium from killing the adb server instance',
|
||||
}],
|
||||
|
||||
[['--long-stacktrace'], {
|
||||
dest: 'longStacktrace',
|
||||
@@ -273,28 +242,11 @@ const serverArgs = [
|
||||
help: 'Add long stack traces to log entries. Recommended for debugging only.',
|
||||
}],
|
||||
|
||||
[['--webkit-debug-proxy-port'], {
|
||||
default: 27753,
|
||||
dest: 'webkitDebugProxyPort',
|
||||
action: StoreDeprecatedDefaultCapabilityAction,
|
||||
required: false,
|
||||
type: 'int',
|
||||
help: '(IOS-only) Local port used for communication with ios-webkit-debug-proxy'
|
||||
}],
|
||||
|
||||
[['--webdriveragent-port'], {
|
||||
default: 8100,
|
||||
dest: 'wdaLocalPort',
|
||||
action: StoreDeprecatedDefaultCapabilityAction,
|
||||
required: false,
|
||||
type: 'int',
|
||||
help: '(IOS-only, XCUITest-only) Local port used for communication with WebDriverAgent'
|
||||
}],
|
||||
|
||||
[['-dc', DEFAULT_CAPS_ARG], {
|
||||
dest: 'defaultCapabilities',
|
||||
default: {},
|
||||
type: parseDefaultCaps,
|
||||
type: parseJsonStringOrFile,
|
||||
required: false,
|
||||
help: 'Set the default desired capabilities, which will be set on each ' +
|
||||
'session unless overridden by received capabilities. For example: ' +
|
||||
@@ -338,6 +290,28 @@ const serverArgs = [
|
||||
'enabled even if also listed in --allow-insecure, and even if --relaxed-security ' +
|
||||
'is turned on. For example: execute_driver_script,adb_shell',
|
||||
}],
|
||||
|
||||
[['--driver-args'], {
|
||||
dest: 'driverArgs',
|
||||
default: {},
|
||||
type: parseJsonStringOrFile,
|
||||
required: false,
|
||||
help: 'Set the default desired client arguments for a driver, ' +
|
||||
'For example: ' +
|
||||
'[ \'{"xcuitest": {"foo1": "bar1", "foo2": "bar2"}}\' ' +
|
||||
'| /path/to/driverArgs.json ]'
|
||||
}],
|
||||
|
||||
[['--plugin-args'], {
|
||||
dest: 'pluginArgs',
|
||||
default: {},
|
||||
type: parseJsonStringOrFile,
|
||||
required: false,
|
||||
help: 'Set the default desired client arguments for a plugin, ' +
|
||||
'For example: ' +
|
||||
'[ \'{"images": {"foo1": "bar1", "foo2": "bar2"}}\' ' +
|
||||
'| /path/to/pluginArgs.json ]'
|
||||
}],
|
||||
];
|
||||
|
||||
// this set of args works for both drivers and plugins ('extensions')
|
||||
@@ -432,11 +406,29 @@ function makeUpdateArgs (type) {
|
||||
];
|
||||
}
|
||||
|
||||
function makeRunArgs (type) {
|
||||
return [
|
||||
...globalExtensionArgs,
|
||||
[[type], {
|
||||
type: 'str',
|
||||
help: `Name of the ${type} to run a script from, for example: ` +
|
||||
type === DRIVER_TYPE ? DRIVER_EXAMPLE : PLUGIN_EXAMPLE,
|
||||
}],
|
||||
[['scriptName'], {
|
||||
default: null,
|
||||
type: 'str',
|
||||
help: `Name of the script to run from the ${type}. The script name must be cached ` +
|
||||
`inside the "scripts" field under "appium" inside the ${type}'s "package.json" file`
|
||||
}],
|
||||
];
|
||||
}
|
||||
|
||||
for (const type of [DRIVER_TYPE, PLUGIN_TYPE]) {
|
||||
extensionArgs[type].list = makeListArgs(type);
|
||||
extensionArgs[type].install = makeInstallArgs(type);
|
||||
extensionArgs[type].uninstall = makeUninstallArgs(type);
|
||||
extensionArgs[type].update = makeUpdateArgs(type);
|
||||
extensionArgs[type].run = makeRunArgs(type);
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@@ -24,6 +24,10 @@ export default class DriverCommand extends ExtensionCommand {
|
||||
return await super.update({ext: driver, unsafe});
|
||||
}
|
||||
|
||||
async run ({driver, scriptName}) {
|
||||
return await super.run({ext: driver, scriptName});
|
||||
}
|
||||
|
||||
getPostInstallText ({extName, extData}) {
|
||||
return `Driver ${extName}@${extData.version} successfully installed\n`.green +
|
||||
`- automationName: ${extData.automationName.green}\n` +
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
import _ from 'lodash';
|
||||
import NPM from './npm';
|
||||
import path from 'path';
|
||||
import { fs, util } from 'appium-support';
|
||||
import { log, spinWith } from './utils';
|
||||
import { fs, util } from '@appium/support';
|
||||
import { log, spinWith, RingBuffer } from './utils';
|
||||
import { SubProcess} from 'teen_process';
|
||||
import { INSTALL_TYPE_NPM, INSTALL_TYPE_GIT, INSTALL_TYPE_GITHUB,
|
||||
INSTALL_TYPE_LOCAL } from '../extension-config';
|
||||
|
||||
@@ -482,8 +483,71 @@ export default class ExtensionCommand {
|
||||
*/
|
||||
async updateExtension (ext, version) {
|
||||
const {pkgName} = this.config.installedExtensions[ext];
|
||||
await this.installViaNpm({ext, pkgName, pkgVer: version});
|
||||
this.config.installedExtensions[ext].version = version;
|
||||
await this.config.write();
|
||||
await fs.rimraf(this.config.getInstallPath(ext));
|
||||
const extData = await this.installViaNpm({ext, pkgName, pkgVer: version});
|
||||
delete extData[`${this.type}Name`];
|
||||
await this.config.updateExtension(ext, extData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef RunOutput
|
||||
* @property {?string|undefined} error - error message if script ran unsuccessfuly, otherwise undefined
|
||||
* @property {string[]} output - script output
|
||||
*/
|
||||
/**
|
||||
* Runs a script cached inside the "scripts" field under "appium"
|
||||
* inside of the driver/plugins "package.json" file. Will throw
|
||||
* an error if the driver/plugin does not contain a "scripts" field
|
||||
* underneath the "appium" field in its package.json, if the
|
||||
* "scripts" field is not a plain object, or if the scriptName is
|
||||
* not found within "scripts" object.
|
||||
*
|
||||
* @param {string} ext - name of the extension to run a script from
|
||||
* @param {string} scriptName - name of the script to run
|
||||
* @return {RunOutput}
|
||||
*/
|
||||
async run ({ext, scriptName}) {
|
||||
if (!_.has(this.config.installedExtensions, ext)) {
|
||||
throw new Error(`please install the ${this.type} first`);
|
||||
}
|
||||
|
||||
const extConfig = this.config.installedExtensions[ext];
|
||||
|
||||
if (!_.has(extConfig, 'scripts')) {
|
||||
throw new Error(`The ${this.type} named '${ext}' does not contain the ` +
|
||||
`"scripts" field underneath the "appium" field in its package.json`);
|
||||
}
|
||||
|
||||
const extScripts = extConfig.scripts;
|
||||
|
||||
if (!_.isPlainObject(extScripts)) {
|
||||
throw new Error(`The ${this.type} named '${ext}' "scripts" field must be a plain object`);
|
||||
}
|
||||
|
||||
if (!_.has(extScripts, scriptName)) {
|
||||
throw new Error(`The ${this.type} named '${ext}' does not support the script: '${scriptName}'`);
|
||||
}
|
||||
|
||||
const runner = new SubProcess(process.execPath, [extScripts[scriptName]], {
|
||||
cwd: this.config.getExtensionRequirePath(ext)
|
||||
});
|
||||
|
||||
const output = new RingBuffer(50);
|
||||
|
||||
runner.on('stream-line', (line) => {
|
||||
output.enqueue(line);
|
||||
log(this.isJsonOutput, line);
|
||||
});
|
||||
|
||||
await runner.start(0);
|
||||
|
||||
try {
|
||||
await runner.join();
|
||||
log(this.isJsonOutput, `${scriptName} successfuly ran`.green);
|
||||
return {output: output.getBuff()};
|
||||
} catch (err) {
|
||||
log(this.isJsonOutput, `Encountered an error when running '${scriptName}': ${err.message}`.red);
|
||||
return {error: err.message, output: output.getBuff()};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from 'path';
|
||||
import semver from 'semver';
|
||||
import { exec } from 'teen_process';
|
||||
import { fs, mkdirp, util, system } from 'appium-support';
|
||||
import { fs, mkdirp, util, system } from '@appium/support';
|
||||
|
||||
const INSTALL_LOCKFILE = '.appium.install.lock';
|
||||
const LINK_LOCKFILE = '.appium.link.lock';
|
||||
@@ -159,8 +159,14 @@ export default class NPM {
|
||||
`provided: ${pkgPath}`);
|
||||
}
|
||||
|
||||
// this is added to handle commands with relative paths
|
||||
// ie: "node . driver install --source=local ../fake-driver"
|
||||
pkgPath = path.resolve(process.cwd(), pkgPath);
|
||||
|
||||
const pkgHome = path.resolve(this.appiumHome, pkgName);
|
||||
const res = await this.exec('link', [pkgPath], {cwd: pkgHome, lockFile: LINK_LOCKFILE});
|
||||
|
||||
// call link with --no-package-lock to ensure no corruption while installing local packages
|
||||
const res = await this.exec('link', ['--no-package-lock', pkgPath], {cwd: pkgHome, lockFile: LINK_LOCKFILE});
|
||||
if (res.json && res.json.error) {
|
||||
throw new Error(res.json.error);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ function parsePluginNames (names) {
|
||||
}
|
||||
}
|
||||
|
||||
function parseDefaultCaps (capsOrPath) {
|
||||
function parseJsonStringOrFile (capsOrPath) {
|
||||
let caps = capsOrPath;
|
||||
let loadedFromFile = false;
|
||||
try {
|
||||
@@ -82,8 +82,8 @@ function parseDefaultCaps (capsOrPath) {
|
||||
return result;
|
||||
} catch (e) {
|
||||
const msg = loadedFromFile
|
||||
? `Default capabilities in '${capsOrPath}' must be a valid JSON`
|
||||
: `Default capabilities must be a valid JSON`;
|
||||
? `The provided value of '${capsOrPath}' must be a valid JSON`
|
||||
: `The provided value must be a valid JSON`;
|
||||
throw new TypeError(`${msg}. Original error: ${e.message}`);
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,7 @@ function parseInstallTypes (source) {
|
||||
|
||||
export {
|
||||
parseSecurityFeatures,
|
||||
parseDefaultCaps,
|
||||
parseJsonStringOrFile,
|
||||
parseInstallTypes,
|
||||
parsePluginNames,
|
||||
parseDriverNames,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ArgumentParser } from 'argparse';
|
||||
import { sharedArgs, serverArgs, extensionArgs } from './args';
|
||||
import { DRIVER_TYPE, PLUGIN_TYPE } from '../extension-config';
|
||||
import { rootDir } from '../utils';
|
||||
import { fs } from '@appium/support';
|
||||
|
||||
|
||||
function makeDebugParser (parser) {
|
||||
@@ -23,7 +24,7 @@ function getParser (debug = false) {
|
||||
}
|
||||
parser.add_argument('-v', '--version', {
|
||||
action: 'version',
|
||||
version: require(path.resolve(rootDir, 'package.json')).version
|
||||
version: fs.readPackageJsonFrom(rootDir).version
|
||||
});
|
||||
const subParsers = parser.add_subparsers({dest: 'subcommand'});
|
||||
|
||||
@@ -98,6 +99,9 @@ function addExtensionsToParser (sharedArgs, subParsers, debug = false) {
|
||||
help: `Uninstall a ${type}`},
|
||||
{command: 'update', args: extensionArgs[type].update,
|
||||
help: `Update installed ${type}s to the latest version`},
|
||||
{command: 'run', args: extensionArgs[type].run,
|
||||
help: `Run a script (defined inside the ${type}'s package.json under the ` +
|
||||
`“scripts” field inside the “appium” field) from an installed ${type}`}
|
||||
];
|
||||
|
||||
for (const {command, args, help} of parserSpecs) {
|
||||
|
||||
@@ -24,6 +24,10 @@ export default class PluginCommand extends ExtensionCommand {
|
||||
return await super.update({ext: plugin, unsafe});
|
||||
}
|
||||
|
||||
async run ({plugin, scriptName}) {
|
||||
return await super.run({ext: plugin, scriptName});
|
||||
}
|
||||
|
||||
getPostInstallText ({extName, extData}) {
|
||||
return `Plugin ${extName}@${extData.version} successfully installed`.green;
|
||||
}
|
||||
|
||||
@@ -52,9 +52,29 @@ async function spinWith (json, msg, fn) {
|
||||
}
|
||||
}
|
||||
|
||||
class RingBuffer {
|
||||
constructor (size = 50) {
|
||||
this.size = size;
|
||||
this.buffer = [];
|
||||
}
|
||||
getBuff () {
|
||||
return this.buffer;
|
||||
}
|
||||
dequeue () {
|
||||
this.buffer.shift();
|
||||
}
|
||||
enqueue (item) {
|
||||
if (this.buffer.length >= this.size) {
|
||||
this.dequeue();
|
||||
}
|
||||
this.buffer.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
errAndQuit,
|
||||
log,
|
||||
spinWith,
|
||||
JSON_SPACES
|
||||
JSON_SPACES,
|
||||
RingBuffer
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import path from 'path';
|
||||
import { mkdirp, system } from 'appium-support';
|
||||
import { mkdirp, system, fs } from '@appium/support';
|
||||
import axios from 'axios';
|
||||
import { exec } from 'teen_process';
|
||||
import { rootDir } from './utils';
|
||||
@@ -11,7 +10,8 @@ import {
|
||||
} from './cli/argparse-actions';
|
||||
import findUp from 'find-up';
|
||||
|
||||
const npmPackage = require(path.resolve(rootDir, 'package.json'));
|
||||
const npmPackage = fs.readPackageJsonFrom(__dirname);
|
||||
|
||||
const APPIUM_VER = npmPackage.version;
|
||||
const MIN_NODE_VERSION = npmPackage.engines.node;
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ function findMatchingDriver (config, {automationName, platformName}) {
|
||||
if (!driver) {
|
||||
throw new Error(`MainClass ${mainClass} did not result in a driver object`);
|
||||
}
|
||||
return {driver, version};
|
||||
return {driver, version, driverName};
|
||||
} catch (err) {
|
||||
const msg = `Could not find a driver for automationName ` +
|
||||
`'${automationName}' and platformName ${platformName}'. ` +
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import log from './logger';
|
||||
import { fs, mkdirp } from 'appium-support';
|
||||
import { fs, mkdirp } from '@appium/support';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import YAML from 'yaml';
|
||||
@@ -160,6 +160,14 @@ export default class ExtensionConfig {
|
||||
await this.write();
|
||||
}
|
||||
|
||||
async updateExtension (extName, extData) {
|
||||
this.installedExtensions[extName] = {
|
||||
...this.installedExtensions[extName],
|
||||
...extData,
|
||||
};
|
||||
await this.write();
|
||||
}
|
||||
|
||||
async removeExtension (extName) {
|
||||
delete this.installedExtensions[extName];
|
||||
await this.write();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { fs } from 'appium-support';
|
||||
import { fs } from '@appium/support';
|
||||
import logger from './logger';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { logger } from 'appium-support';
|
||||
import { logger } from '@appium/support';
|
||||
|
||||
|
||||
let log = logger.getLogger('Appium');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import npmlog from 'npmlog';
|
||||
import { createLogger, format, transports } from 'winston';
|
||||
import { fs, logger } from 'appium-support';
|
||||
import { fs, logger } from '@appium/support';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { server as baseServer, routeConfiguringFunction as makeRouter } from '@a
|
||||
import { asyncify } from 'asyncbox';
|
||||
import { default as getParser, getDefaultServerArgs } from './cli/parser';
|
||||
import { USE_ALL_PLUGINS } from './cli/args';
|
||||
import { logger as logFactory, util } from 'appium-support';
|
||||
import { logger as logFactory, util } from '@appium/support';
|
||||
import {
|
||||
showConfig, checkNodeOk, validateServerArgs,
|
||||
warnNodeDeprecations, validateTmpDir, getNonDefaultArgs,
|
||||
@@ -200,14 +200,13 @@ async function main (args = null) {
|
||||
appiumDriver.driverConfig = driverConfig;
|
||||
const pluginConfig = new PluginConfig(args.appiumHome);
|
||||
await preflightChecks({parser, args, driverConfig, pluginConfig, throwInsteadOfExit});
|
||||
const pluginClasses = getActivePlugins(args, pluginConfig);
|
||||
// set the active plugins on the umbrella driver so it can use them for commands
|
||||
appiumDriver.pluginClasses = pluginClasses;
|
||||
await logStartupInfo(parser, args);
|
||||
let routeConfiguringFunction = makeRouter(appiumDriver);
|
||||
|
||||
const driverClasses = getActiveDrivers(args, driverConfig);
|
||||
const pluginClasses = getActivePlugins(args, pluginConfig);
|
||||
// set the active plugins on the umbrella driver so it can use them for commands
|
||||
appiumDriver.pluginClasses = pluginClasses;
|
||||
|
||||
const serverUpdaters = getServerUpdaters(driverClasses, pluginClasses);
|
||||
const extraMethodMap = getExtraMethodMap(driverClasses, pluginClasses);
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import _ from 'lodash';
|
||||
import logger from './logger';
|
||||
import { processCapabilities, PROTOCOLS } from '@appium/base-driver';
|
||||
import findRoot from 'find-root';
|
||||
import { parseJsonStringOrFile } from './cli/parser-helpers';
|
||||
import { fs } from '@appium/support';
|
||||
|
||||
const W3C_APPIUM_PREFIX = 'appium';
|
||||
|
||||
@@ -33,6 +34,18 @@ function inspectObject (args) {
|
||||
}
|
||||
}
|
||||
|
||||
function parseExtensionArgs (extensionArgs, extensionName) {
|
||||
if (!_.isString(extensionArgs)) {
|
||||
return {};
|
||||
}
|
||||
const parsedExtensionArgs = parseJsonStringOrFile(extensionArgs);
|
||||
const extensionSpecificArgs = parsedExtensionArgs[extensionName];
|
||||
if (!_.isPlainObject(extensionSpecificArgs)) {
|
||||
throw new Error(`Driver or plugin arguments must be plain objects`);
|
||||
}
|
||||
return extensionSpecificArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the caps that were provided in the request and translates them
|
||||
* into caps that can be used by the inner drivers.
|
||||
@@ -216,9 +229,9 @@ function pullSettings (caps) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const rootDir = findRoot(__dirname);
|
||||
const rootDir = fs.findRoot(__dirname);
|
||||
|
||||
export {
|
||||
inspectObject, parseCapsForInnerDriver, insertAppiumPrefixes, rootDir,
|
||||
getPackageVersion, pullSettings, removeAppiumPrefixes,
|
||||
getPackageVersion, pullSettings, removeAppiumPrefixes, parseExtensionArgs,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "appium",
|
||||
"version": "2.0.0-beta.12",
|
||||
"version": "2.0.0-beta.13",
|
||||
"description": "Automation for Apps.",
|
||||
"keywords": [
|
||||
"automation",
|
||||
@@ -36,49 +36,54 @@
|
||||
"postinstall.js"
|
||||
],
|
||||
"scripts": {
|
||||
"backup-shrinkwrap": "mv npm-shrinkwrap.json npm-shrinkwrap-backup.json",
|
||||
"check-pruned-shrinkwrap": "node check-pruned-shrinkwrap.js",
|
||||
"generate-docs": "gulp transpile && node ./build/commands-yml/parse.js",
|
||||
"postinstall": "node ./postinstall.js",
|
||||
"install-fake-driver": "node . driver install --source=npm appium-fake-driver",
|
||||
"prepublishOnly": "npm run prune-shrinkwrap && gulp fixShrinkwrap",
|
||||
"prune-shrinkwrap": "!(test -e npm-shrinkwrap.json) || (npm ci --production --ignore-scripts && npm run backup-shrinkwrap && npm shrinkwrap)",
|
||||
"postpublish": "npm run restore-shrinkwrap",
|
||||
"restore-shrinkwrap": "!(test -e npm-shrinkwrap.json) || (mv npm-shrinkwrap-backup.json npm-shrinkwrap.json)",
|
||||
"install-fake-driver": "node . driver install --source=local ../fake-driver",
|
||||
"prepublishOnly": "npm run shrinkwrap:prune && npm run shrinkwrap:fix",
|
||||
"postpublish": "npm run shrinkwrap:restore",
|
||||
"shrinkwrap:backup": "mv npm-shrinkwrap.json npm-shrinkwrap-backup.json",
|
||||
"shrinkwrap:check-pruned": "node check-pruned-shrinkwrap.js",
|
||||
"shrinkwrap:fix": "gulp fixShrinkwrap",
|
||||
"shrinkwrap:prune": "!(test -e npm-shrinkwrap.json) || (npm ci --production --ignore-scripts && npm run backup-shrinkwrap && npm shrinkwrap)",
|
||||
"shrinkwrap:restore": "(test -e npm-shrinkwrap.json) || (mv npm-shrinkwrap-backup.json npm-shrinkwrap.json)",
|
||||
"upload": "gulp github-upload",
|
||||
"zip": "zip -qr appium.zip .",
|
||||
"zip-and-upload": "npm run zip && npm run upload"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appium/base-plugin": "^1.0.0",
|
||||
"@babel/runtime": "^7.6.0",
|
||||
"@appium/base-driver": "^8.0.0-beta.6",
|
||||
"@appium/support": "2.x",
|
||||
"@appium/base-driver": "^8.0.0-beta.7",
|
||||
"@appium/base-plugin": "^1.7.0",
|
||||
"@appium/support": "^2.54.0",
|
||||
"@babel/runtime": "^7.15.3",
|
||||
"argparse": "^2.0.1",
|
||||
"async-lock": "^1.0.0",
|
||||
"async-lock": "^1.3.0",
|
||||
"asyncbox": "2.x",
|
||||
"axios": "^0.21.0",
|
||||
"bluebird": "3.x",
|
||||
"continuation-local-storage": "3.x",
|
||||
"find-root": "^1.1.0",
|
||||
"find-up": "^5.0.0",
|
||||
"lodash": "^4.17.11",
|
||||
"lodash": "^4.17.21",
|
||||
"longjohn": "^0.2.12",
|
||||
"npmlog": "4.x",
|
||||
"ora": "^4.0.4",
|
||||
"ora": "^5.4.1",
|
||||
"semver": "^7.0.0",
|
||||
"source-map-support": "0.x",
|
||||
"teen_process": "1.x",
|
||||
"winston": "3.x",
|
||||
"word-wrap": "^1.2.3",
|
||||
"yaml": "^1.7.2"
|
||||
"yaml": "^1.10.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/fake-driver": "^3.0.0",
|
||||
"@appium/gulp-plugins": "^5.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/fake-driver": "^2.2.0",
|
||||
"@appium/gulp-plugins": "^5.5.0"
|
||||
}
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"tag": "next"
|
||||
},
|
||||
"homepage": "https://appium.io"
|
||||
}
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
|
||||
import path from 'path';
|
||||
import { exec } from 'teen_process';
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import { tempDir, fs, mkdirp, util } from 'appium-support';
|
||||
import { tempDir, fs, mkdirp, util } from '@appium/support';
|
||||
import { KNOWN_DRIVERS } from '../lib/drivers';
|
||||
import { PROJECT_ROOT as cwd } from './helpers';
|
||||
|
||||
chai.should();
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
// cannot use `require.resolve()` here (w/o acrobatics) due to the ESM context.
|
||||
// could also derive it from the `package.json` if we wanted
|
||||
const executable = path.join(cwd, 'packages', 'appium', 'build', 'lib', 'main.js');
|
||||
|
||||
describe('Driver CLI', function () {
|
||||
describe('CLI', function () {
|
||||
let appiumHome;
|
||||
|
||||
before(async function () {
|
||||
@@ -29,121 +26,177 @@ describe('Driver CLI', function () {
|
||||
await mkdirp(appiumHome);
|
||||
}
|
||||
|
||||
async function run (driverCmd, args = [], raw = false) {
|
||||
async function run (driverCmd, args = [], raw = false, ext = 'driver') {
|
||||
args = [...args, '--appium-home', appiumHome];
|
||||
const ret = await exec('node', [executable, 'driver', driverCmd, ...args], {cwd});
|
||||
const ret = await exec('node', [executable, ext, driverCmd, ...args], {cwd});
|
||||
if (raw) {
|
||||
return ret;
|
||||
}
|
||||
return ret.stdout;
|
||||
}
|
||||
describe('Driver CLI', function () {
|
||||
const localFakeDriverPath = path.resolve(__dirname, '..', '..', '..', 'fake-driver');
|
||||
describe('list', function () {
|
||||
it('should list available drivers', async function () {
|
||||
const stdout = await run('list');
|
||||
for (const d of Object.keys(KNOWN_DRIVERS)) {
|
||||
stdout.should.match(new RegExp(`${d}.+[not installed]`));
|
||||
}
|
||||
});
|
||||
it('should list available drivers in json format', async function () {
|
||||
const driverData = JSON.parse(await run('list', ['--json']));
|
||||
for (const d of Object.keys(KNOWN_DRIVERS)) {
|
||||
driverData[d].should.eql({installed: false, pkgName: KNOWN_DRIVERS[d]});
|
||||
}
|
||||
});
|
||||
it('should allow filtering by installed drivers', async function () {
|
||||
const out = await run('list', ['--installed', '--json']);
|
||||
JSON.parse(out).should.eql({});
|
||||
});
|
||||
it('should show updates for installed drivers with --updates', async function () {
|
||||
await clear();
|
||||
await run('install', ['appium-fake-driver@0.9.0', '--source', 'npm', '--json']);
|
||||
const {fake} = JSON.parse(await run('list', ['--updates', '--json']));
|
||||
util.compareVersions(fake.updateVersion, '>', '0.9.0').should.be.true;
|
||||
const stdout = await run('list', ['--updates']);
|
||||
stdout.should.match(new RegExp(`fake.+[${fake.updateVersion} available]`));
|
||||
});
|
||||
});
|
||||
|
||||
describe('list', function () {
|
||||
it('should list available drivers', async function () {
|
||||
const stdout = await run('list');
|
||||
for (const d of Object.keys(KNOWN_DRIVERS)) {
|
||||
stdout.should.match(new RegExp(`${d}.+[not installed]`));
|
||||
}
|
||||
describe('install', function () {
|
||||
it('should install a driver from the list of known drivers', async function () {
|
||||
await clear();
|
||||
const ret = JSON.parse(await run('install', ['uiautomator2', '--json']));
|
||||
ret.uiautomator2.pkgName.should.eql('appium-uiautomator2-driver');
|
||||
ret.uiautomator2.installType.should.eql('npm');
|
||||
ret.uiautomator2.installSpec.should.eql('uiautomator2');
|
||||
const list = JSON.parse(await run('list', ['--installed', '--json']));
|
||||
delete list.uiautomator2.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
it('should install a driver from npm', async function () {
|
||||
await clear();
|
||||
const ret = JSON.parse(await run('install', ['appium-fake-driver', '--source', 'npm', '--json']));
|
||||
ret.fake.pkgName.should.eql('appium-fake-driver');
|
||||
ret.fake.installType.should.eql('npm');
|
||||
ret.fake.installSpec.should.eql('appium-fake-driver');
|
||||
const list = JSON.parse(await run('list', ['--installed', '--json']));
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
it('should install a driver from npm with a specific version/tag', async function () {
|
||||
await clear();
|
||||
const ret = JSON.parse(await run('install', ['appium-fake-driver@0.9.0', '--source', 'npm', '--json']));
|
||||
ret.fake.pkgName.should.eql('appium-fake-driver');
|
||||
ret.fake.installType.should.eql('npm');
|
||||
ret.fake.installSpec.should.eql('appium-fake-driver@0.9.0');
|
||||
const list = JSON.parse(await run('list', ['--installed', '--json']));
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
it('should install a driver from github', async function () {
|
||||
await clear();
|
||||
const ret = JSON.parse(await run('install', ['appium/appium-fake-driver', '--source',
|
||||
'github', '--package', 'appium-fake-driver', '--json']));
|
||||
ret.fake.pkgName.should.eql('appium-fake-driver');
|
||||
ret.fake.installType.should.eql('github');
|
||||
ret.fake.installSpec.should.eql('appium/appium-fake-driver');
|
||||
const list = JSON.parse(await run('list', ['--installed', '--json']));
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
it('should install a driver from git', async function () {
|
||||
await clear();
|
||||
const ret = JSON.parse(await run('install', ['git+https://github.com/appium/appium-fake-driver.git',
|
||||
'--source', 'git', '--package', 'appium-fake-driver', '--json']));
|
||||
ret.fake.pkgName.should.eql('appium-fake-driver');
|
||||
ret.fake.installType.should.eql('git');
|
||||
ret.fake.installSpec.should.eql('git+https://github.com/appium/appium-fake-driver');
|
||||
const list = JSON.parse(await run('list', ['--installed', '--json']));
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
it('should install a driver from a local npm module', async function () {
|
||||
await clear();
|
||||
// take advantage of the fact that we know we have fake driver installed as a dependency in
|
||||
// this module, so we know its local path on disk
|
||||
const ret = JSON.parse(await run('install', [localFakeDriverPath, '--source', 'local', '--json']));
|
||||
ret.fake.pkgName.should.eql('@appium/fake-driver');
|
||||
ret.fake.installType.should.eql('local');
|
||||
ret.fake.installSpec.should.eql(localFakeDriverPath);
|
||||
const list = JSON.parse(await run('list', ['--installed', '--json']));
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
});
|
||||
it('should list available drivers in json format', async function () {
|
||||
const driverData = JSON.parse(await run('list', ['--json']));
|
||||
for (const d of Object.keys(KNOWN_DRIVERS)) {
|
||||
driverData[d].should.eql({installed: false, pkgName: KNOWN_DRIVERS[d]});
|
||||
}
|
||||
|
||||
describe('uninstall', function () {
|
||||
it('should uninstall a driver based on its driver name', async function () {
|
||||
await clear();
|
||||
const ret = JSON.parse(await run('install', ['appium-fake-driver', '--source', 'npm', '--json']));
|
||||
const installPath = path.resolve(appiumHome, ret.fake.installPath);
|
||||
await fs.exists(installPath).should.eventually.be.true;
|
||||
let list = JSON.parse(await run('list', ['--installed', '--json']));
|
||||
list.fake.installed.should.be.true;
|
||||
const uninstall = JSON.parse(await run('uninstall', ['fake', '--json']));
|
||||
uninstall.should.not.have.key('fake');
|
||||
await fs.exists(installPath).should.eventually.be.false;
|
||||
});
|
||||
});
|
||||
it('should allow filtering by installed drivers', async function () {
|
||||
const out = await run('list', ['--installed', '--json']);
|
||||
JSON.parse(out).should.eql({});
|
||||
});
|
||||
it('should show updates for installed drivers with --updates', async function () {
|
||||
await clear();
|
||||
await run('install', ['appium-fake-driver@0.9.0', '--source', 'npm', '--json']);
|
||||
const {fake} = JSON.parse(await run('list', ['--updates', '--json']));
|
||||
util.compareVersions(fake.updateVersion, '>', '0.9.0').should.be.true;
|
||||
const stdout = await run('list', ['--updates']);
|
||||
stdout.should.match(new RegExp(`fake.+[${fake.updateVersion} available]`));
|
||||
|
||||
describe('run', function () {
|
||||
before(async function () {
|
||||
await clear();
|
||||
await run('install', [localFakeDriverPath, '--source', 'local', '--json']);
|
||||
});
|
||||
it('should run a valid driver, valid script, and result in success', async function () {
|
||||
const driverName = 'fake';
|
||||
const scriptName = 'fake-success';
|
||||
const out = JSON.parse(await run('run', [driverName, scriptName, '--json']));
|
||||
out.should.not.include.key('error');
|
||||
});
|
||||
it('should run a valid driver, valid error prone script, and return error in json', async function () {
|
||||
const driverName = 'fake';
|
||||
const out = JSON.parse(await run('run', [driverName, 'fake-error', '--json']));
|
||||
out.should.include.key('error');
|
||||
});
|
||||
it('should take a valid driver, invalid script, and throw an error', async function () {
|
||||
const driverName = 'fake';
|
||||
await chai.expect(run('run', [driverName, 'foo', '--json'])).to.eventually.be.rejectedWith(Error);
|
||||
});
|
||||
it('should take an invalid driver, invalid script, and throw an error', async function () {
|
||||
const driverName = 'foo';
|
||||
await chai.expect(run('run', [driverName, 'bar', '--json'])).to.eventually.be.rejectedWith(Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('install', function () {
|
||||
it('should install a driver from the list of known drivers', async function () {
|
||||
await clear();
|
||||
const ret = JSON.parse(await run('install', ['uiautomator2', '--json']));
|
||||
ret.uiautomator2.pkgName.should.eql('appium-uiautomator2-driver');
|
||||
ret.uiautomator2.installType.should.eql('npm');
|
||||
ret.uiautomator2.installSpec.should.eql('uiautomator2');
|
||||
const list = JSON.parse(await run('list', ['--installed', '--json']));
|
||||
delete list.uiautomator2.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
it('should install a driver from npm', async function () {
|
||||
await clear();
|
||||
const ret = JSON.parse(await run('install', ['appium-fake-driver', '--source', 'npm', '--json']));
|
||||
ret.fake.pkgName.should.eql('appium-fake-driver');
|
||||
ret.fake.installType.should.eql('npm');
|
||||
ret.fake.installSpec.should.eql('appium-fake-driver');
|
||||
const list = JSON.parse(await run('list', ['--installed', '--json']));
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
it('should install a driver from npm with a specific version/tag', async function () {
|
||||
await clear();
|
||||
const ret = JSON.parse(await run('install', ['appium-fake-driver@0.9.0', '--source', 'npm', '--json']));
|
||||
ret.fake.pkgName.should.eql('appium-fake-driver');
|
||||
ret.fake.installType.should.eql('npm');
|
||||
ret.fake.installSpec.should.eql('appium-fake-driver@0.9.0');
|
||||
const list = JSON.parse(await run('list', ['--installed', '--json']));
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
it('should install a driver from github', async function () {
|
||||
await clear();
|
||||
const ret = JSON.parse(await run('install', ['appium/appium-fake-driver', '--source',
|
||||
'github', '--package', 'appium-fake-driver', '--json']));
|
||||
ret.fake.pkgName.should.eql('appium-fake-driver');
|
||||
ret.fake.installType.should.eql('github');
|
||||
ret.fake.installSpec.should.eql('appium/appium-fake-driver');
|
||||
const list = JSON.parse(await run('list', ['--installed', '--json']));
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
it('should install a driver from git', async function () {
|
||||
await clear();
|
||||
const ret = JSON.parse(await run('install', ['git+https://github.com/appium/appium-fake-driver.git',
|
||||
'--source', 'git', '--package', 'appium-fake-driver', '--json']));
|
||||
ret.fake.pkgName.should.eql('appium-fake-driver');
|
||||
ret.fake.installType.should.eql('git');
|
||||
ret.fake.installSpec.should.eql('git+https://github.com/appium/appium-fake-driver');
|
||||
const list = JSON.parse(await run('list', ['--installed', '--json']));
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
it('should install a driver from a local npm module', async function () {
|
||||
await clear();
|
||||
// take advantage of the fact that we know we have fake driver installed as a dependency in
|
||||
// this module, so we know its local path on disk
|
||||
const localFakeDriverPath = path.resolve(cwd, 'packages', 'fake-driver');
|
||||
const ret = JSON.parse(await run('install', [localFakeDriverPath, '--source', 'local', '--json']));
|
||||
ret.fake.pkgName.should.eql('@appium/fake-driver');
|
||||
ret.fake.installType.should.eql('local');
|
||||
ret.fake.installSpec.should.eql(localFakeDriverPath);
|
||||
const list = JSON.parse(await run('list', ['--installed', '--json']));
|
||||
delete list.fake.installed;
|
||||
list.should.eql(ret);
|
||||
});
|
||||
});
|
||||
|
||||
describe('uninstall', function () {
|
||||
it('should uninstall a driver based on its driver name', async function () {
|
||||
await clear();
|
||||
const ret = JSON.parse(await run('install', ['appium-fake-driver', '--source', 'npm', '--json']));
|
||||
const installPath = path.resolve(appiumHome, ret.fake.installPath);
|
||||
await fs.exists(installPath).should.eventually.be.true;
|
||||
let list = JSON.parse(await run('list', ['--installed', '--json']));
|
||||
list.fake.installed.should.be.true;
|
||||
const uninstall = JSON.parse(await run('uninstall', ['fake', '--json']));
|
||||
uninstall.should.not.have.key('fake');
|
||||
await fs.exists(installPath).should.eventually.be.false;
|
||||
describe('Plugin CLI', function () {
|
||||
const fakePluginDir = path.resolve(__dirname, '..', '..', '..', '..', 'node_modules', '@appium', 'fake-plugin');
|
||||
const ext = 'plugin';
|
||||
describe('run', function () {
|
||||
before(async function () {
|
||||
await clear();
|
||||
await run('install', [fakePluginDir, '--source', 'local', '--json'], false, ext);
|
||||
});
|
||||
it('should run a valid plugin, valid script, and result in success', async function () {
|
||||
const pluginName = 'fake';
|
||||
const scriptName = 'fake-success';
|
||||
const out = JSON.parse(await run('run', [pluginName, scriptName, '--json'], false, ext));
|
||||
out.should.not.include.key('error');
|
||||
});
|
||||
it('should run a valid plugin, valid error prone script, and return error in json', async function () {
|
||||
const pluginName = 'fake';
|
||||
const out = JSON.parse(await run('run', [pluginName, 'fake-error', '--json'], false, ext));
|
||||
out.should.include.key('error');
|
||||
});
|
||||
it('should take a valid plugin, invalid script, and throw an error', async function () {
|
||||
const pluginName = 'fake';
|
||||
await chai.expect(run('run', [pluginName, 'foo', '--json'], false, ext)).to.eventually.be.rejectedWith(Error);
|
||||
});
|
||||
it('should take an invalid plugin, invalid script, and throw an error', async function () {
|
||||
await chai.expect(run('run', ['foo', 'bar', '--json'], false, ext)).to.eventually.be.rejectedWith(Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
// transpile:mocha
|
||||
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import { DEFAULT_APPIUM_HOME } from '../lib/extension-config';
|
||||
import DriverConfig from '../lib/driver-config';
|
||||
import DriverCommand from '../lib/cli/driver-command';
|
||||
import sinon from 'sinon';
|
||||
|
||||
chai.should();
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
describe('DriverCommand', function () {
|
||||
const config = new DriverConfig(DEFAULT_APPIUM_HOME);
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import sinon from 'sinon';
|
||||
import {
|
||||
getGitRev,
|
||||
getBuildInfo,
|
||||
updateBuildInfo,
|
||||
APPIUM_VER,
|
||||
} from '../lib/config';
|
||||
import axios from 'axios';
|
||||
import { fs } from '@appium/support';
|
||||
|
||||
|
||||
describe('Config', function () {
|
||||
describe('getGitRev', function () {
|
||||
it('should get a reasonable git revision', async function () {
|
||||
let rev = await getGitRev();
|
||||
rev.should.be.a('string');
|
||||
rev.length.should.be.equal(40);
|
||||
rev.match(/[0-9a-f]+/i)[0].should.eql(rev);
|
||||
});
|
||||
});
|
||||
describe('getBuildInfo', function () {
|
||||
async function verifyBuildInfoUpdate (useLocalGit) {
|
||||
const buildInfo = getBuildInfo();
|
||||
mockFs.expects('exists').atLeast(1).returns(useLocalGit);
|
||||
buildInfo['git-sha'] = undefined;
|
||||
buildInfo.built = undefined;
|
||||
await updateBuildInfo(true);
|
||||
buildInfo.should.be.an('object');
|
||||
should.exist(buildInfo['git-sha']);
|
||||
should.exist(buildInfo.built);
|
||||
should.exist(buildInfo.version);
|
||||
}
|
||||
|
||||
let mockFs;
|
||||
let getStub;
|
||||
beforeEach(function () {
|
||||
mockFs = sinon.mock(fs);
|
||||
getStub = sinon.stub(axios, 'get');
|
||||
});
|
||||
afterEach(function () {
|
||||
getStub.restore();
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('should get a configuration object if the local git metadata is present', async function () {
|
||||
await verifyBuildInfoUpdate(true);
|
||||
});
|
||||
it('should get a configuration object if the local git metadata is not present', async function () {
|
||||
getStub.onCall(0).returns({
|
||||
data: [
|
||||
{
|
||||
name: `v${APPIUM_VER}`,
|
||||
zipball_url:
|
||||
'https://api.github.com/repos/appium/appium/zipball/v1.9.0-beta.1',
|
||||
tarball_url:
|
||||
'https://api.github.com/repos/appium/appium/tarball/v1.9.0-beta.1',
|
||||
commit: {
|
||||
sha: '3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
|
||||
url: 'https://api.github.com/repos/appium/appium/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
|
||||
},
|
||||
node_id: 'MDM6UmVmNzUzMDU3MDp2MS45LjAtYmV0YS4x',
|
||||
},
|
||||
{
|
||||
name: 'v1.8.2-beta',
|
||||
zipball_url:
|
||||
'https://api.github.com/repos/appium/appium/zipball/v1.8.2-beta',
|
||||
tarball_url:
|
||||
'https://api.github.com/repos/appium/appium/tarball/v1.8.2-beta',
|
||||
commit: {
|
||||
sha: '5b98b9197e75aa85e7507d21d3126c1a63d1ce8f',
|
||||
url: 'https://api.github.com/repos/appium/appium/commits/5b98b9197e75aa85e7507d21d3126c1a63d1ce8f',
|
||||
},
|
||||
node_id: 'MDM6UmVmNzUzMDU3MDp2MS44LjItYmV0YQ==',
|
||||
},
|
||||
],
|
||||
});
|
||||
getStub.onCall(1).returns({
|
||||
data: {
|
||||
sha: '3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
|
||||
node_id:
|
||||
'MDY6Q29tbWl0NzUzMDU3MDozYzI3NTJmOWY5YzU2MDAwNzA1YTRhZTE1YjNiYTY4YTVkMmU2NDRj',
|
||||
commit: {
|
||||
author: {
|
||||
name: 'Isaac Murchie',
|
||||
email: 'isaac@saucelabs.com',
|
||||
date: '2018-08-17T19:48:00Z',
|
||||
},
|
||||
committer: {
|
||||
name: 'Isaac Murchie',
|
||||
email: 'isaac@saucelabs.com',
|
||||
date: '2018-08-17T19:48:00Z',
|
||||
},
|
||||
message: 'v1.9.0-beta.1',
|
||||
tree: {
|
||||
sha: '2c0974727470eba419ea0b9951c52f72f8036b18',
|
||||
url: 'https://api.github.com/repos/appium/appium/git/trees/2c0974727470eba419ea0b9951c52f72f8036b18',
|
||||
},
|
||||
url: 'https://api.github.com/repos/appium/appium/git/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
|
||||
comment_count: 0,
|
||||
verification: {
|
||||
verified: false,
|
||||
reason: 'unsigned',
|
||||
signature: null,
|
||||
payload: null,
|
||||
},
|
||||
},
|
||||
url: 'https://api.github.com/repos/appium/appium/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
|
||||
html_url:
|
||||
'https://github.com/appium/appium/commit/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
|
||||
comments_url:
|
||||
'https://api.github.com/repos/appium/appium/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c/comments',
|
||||
},
|
||||
});
|
||||
await verifyBuildInfoUpdate(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,118 +1,15 @@
|
||||
// transpile:mocha
|
||||
|
||||
import _ from 'lodash';
|
||||
import chai from 'chai';
|
||||
import sinon from 'sinon';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import { getGitRev, getBuildInfo, checkNodeOk, warnNodeDeprecations,
|
||||
import { getBuildInfo, checkNodeOk, warnNodeDeprecations,
|
||||
getNonDefaultArgs, validateServerArgs,
|
||||
validateTmpDir, showConfig, checkValidPort, updateBuildInfo,
|
||||
APPIUM_VER } from '../lib/config';
|
||||
validateTmpDir, showConfig, checkValidPort } from '../lib/config';
|
||||
import getParser from '../lib/cli/parser';
|
||||
import logger from '../lib/logger';
|
||||
import { fs } from 'appium-support';
|
||||
import axios from 'axios';
|
||||
|
||||
let should = chai.should();
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
|
||||
describe('Config', function () {
|
||||
describe('getGitRev', function () {
|
||||
it('should get a reasonable git revision', async function () {
|
||||
let rev = await getGitRev();
|
||||
rev.should.be.a('string');
|
||||
rev.length.should.be.equal(40);
|
||||
rev.match(/[0-9a-f]+/i)[0].should.eql(rev);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Appium config', function () {
|
||||
describe('getBuildInfo', function () {
|
||||
async function verifyBuildInfoUpdate (useLocalGit) {
|
||||
const buildInfo = getBuildInfo();
|
||||
mockFs.expects('exists').atLeast(1).returns(useLocalGit);
|
||||
buildInfo['git-sha'] = undefined;
|
||||
buildInfo.built = undefined;
|
||||
await updateBuildInfo(true);
|
||||
buildInfo.should.be.an('object');
|
||||
should.exist(buildInfo['git-sha']);
|
||||
should.exist(buildInfo.built);
|
||||
should.exist(buildInfo.version);
|
||||
}
|
||||
|
||||
let mockFs;
|
||||
let getStub;
|
||||
beforeEach(function () {
|
||||
mockFs = sinon.mock(fs);
|
||||
getStub = sinon.stub(axios, 'get');
|
||||
});
|
||||
afterEach(function () {
|
||||
getStub.restore();
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('should get a configuration object if the local git metadata is present', async function () {
|
||||
await verifyBuildInfoUpdate(true);
|
||||
});
|
||||
it('should get a configuration object if the local git metadata is not present', async function () {
|
||||
getStub.onCall(0).returns({data: [
|
||||
{
|
||||
'name': `v${APPIUM_VER}`,
|
||||
'zipball_url': 'https://api.github.com/repos/appium/appium/zipball/v1.9.0-beta.1',
|
||||
'tarball_url': 'https://api.github.com/repos/appium/appium/tarball/v1.9.0-beta.1',
|
||||
'commit': {
|
||||
'sha': '3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
|
||||
'url': 'https://api.github.com/repos/appium/appium/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c'
|
||||
},
|
||||
'node_id': 'MDM6UmVmNzUzMDU3MDp2MS45LjAtYmV0YS4x'
|
||||
},
|
||||
{
|
||||
'name': 'v1.8.2-beta',
|
||||
'zipball_url': 'https://api.github.com/repos/appium/appium/zipball/v1.8.2-beta',
|
||||
'tarball_url': 'https://api.github.com/repos/appium/appium/tarball/v1.8.2-beta',
|
||||
'commit': {
|
||||
'sha': '5b98b9197e75aa85e7507d21d3126c1a63d1ce8f',
|
||||
'url': 'https://api.github.com/repos/appium/appium/commits/5b98b9197e75aa85e7507d21d3126c1a63d1ce8f'
|
||||
},
|
||||
'node_id': 'MDM6UmVmNzUzMDU3MDp2MS44LjItYmV0YQ=='
|
||||
}
|
||||
]});
|
||||
getStub.onCall(1).returns({data: {
|
||||
'sha': '3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
|
||||
'node_id': 'MDY6Q29tbWl0NzUzMDU3MDozYzI3NTJmOWY5YzU2MDAwNzA1YTRhZTE1YjNiYTY4YTVkMmU2NDRj',
|
||||
'commit': {
|
||||
'author': {
|
||||
'name': 'Isaac Murchie',
|
||||
'email': 'isaac@saucelabs.com',
|
||||
'date': '2018-08-17T19:48:00Z'
|
||||
},
|
||||
'committer': {
|
||||
'name': 'Isaac Murchie',
|
||||
'email': 'isaac@saucelabs.com',
|
||||
'date': '2018-08-17T19:48:00Z'
|
||||
},
|
||||
'message': 'v1.9.0-beta.1',
|
||||
'tree': {
|
||||
'sha': '2c0974727470eba419ea0b9951c52f72f8036b18',
|
||||
'url': 'https://api.github.com/repos/appium/appium/git/trees/2c0974727470eba419ea0b9951c52f72f8036b18'
|
||||
},
|
||||
'url': 'https://api.github.com/repos/appium/appium/git/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
|
||||
'comment_count': 0,
|
||||
'verification': {
|
||||
'verified': false,
|
||||
'reason': 'unsigned',
|
||||
'signature': null,
|
||||
'payload': null
|
||||
}
|
||||
},
|
||||
'url': 'https://api.github.com/repos/appium/appium/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
|
||||
'html_url': 'https://github.com/appium/appium/commit/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c',
|
||||
'comments_url': 'https://api.github.com/repos/appium/appium/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c/comments',
|
||||
}});
|
||||
await verifyBuildInfoUpdate(false);
|
||||
});
|
||||
});
|
||||
describe('showConfig', function () {
|
||||
before(function () {
|
||||
sinon.spy(console, 'log');
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
import _ from 'lodash';
|
||||
import path from 'path';
|
||||
import B from 'bluebird';
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import axios from 'axios';
|
||||
import { remote as wdio } from 'webdriverio';
|
||||
import { main as appiumServer } from '../lib/main';
|
||||
@@ -16,13 +14,11 @@ import { runExtensionCommand } from '../lib/cli/extension';
|
||||
import { removeAppiumPrefixes } from '../lib/utils';
|
||||
import sinon from 'sinon';
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
let TEST_SERVER;
|
||||
let TEST_PORT;
|
||||
const FAKE_DRIVER_DIR = path.resolve(__dirname, '..', '..', '..', 'fake-driver');
|
||||
|
||||
const should = chai.should();
|
||||
const FAKE_ARGS = {'sillyWebServerPort': 1234, 'host': 'hey'};
|
||||
const FAKE_DRIVER_ARGS = JSON.stringify({'fake': FAKE_ARGS});
|
||||
const shouldStartServer = process.env.USE_RUNNING_SERVER !== '0';
|
||||
const caps = W3C_PREFIXED_CAPS;
|
||||
const wdOpts = {
|
||||
@@ -38,6 +34,7 @@ describe('FakeDriver - via HTTP', function () {
|
||||
// actually going to be required by Appium
|
||||
let FakeDriver = null;
|
||||
let baseUrl;
|
||||
const FAKE_DRIVER_DIR = path.resolve(__dirname, '..', '..', '..', 'fake-driver');
|
||||
before(async function () {
|
||||
wdOpts.port = TEST_PORT = await getTestPort();
|
||||
TEST_SERVER = `http://${TEST_HOST}:${TEST_PORT}`;
|
||||
@@ -60,19 +57,26 @@ describe('FakeDriver - via HTTP', function () {
|
||||
const config = new DriverConfig(appiumHome);
|
||||
await config.read();
|
||||
FakeDriver = config.require('fake');
|
||||
|
||||
// then start server if we need to
|
||||
if (shouldStartServer) {
|
||||
let args = {port: TEST_PORT, host: TEST_HOST, appiumHome};
|
||||
server = await appiumServer(args);
|
||||
}
|
||||
await serverStart();
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await serverClose();
|
||||
});
|
||||
|
||||
async function serverStart (args = {}) {
|
||||
args = {port: TEST_PORT, host: TEST_HOST, appiumHome, ...args};
|
||||
if (shouldStartServer) {
|
||||
server = await appiumServer(args);
|
||||
}
|
||||
}
|
||||
|
||||
async function serverClose () {
|
||||
if (server) {
|
||||
await server.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe('server updating', function () {
|
||||
it('should allow drivers to update the server in arbitrary ways', async function () {
|
||||
@@ -81,6 +85,35 @@ describe('FakeDriver - via HTTP', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('cli args handling for empty args', function () {
|
||||
it('should not recieve user cli args if none passed in', async function () {
|
||||
let driver = await wdio({...wdOpts, capabilities: caps});
|
||||
const {sessionId} = driver;
|
||||
try {
|
||||
const {data} = await axios.get(`${baseUrl}/${sessionId}/fakedriverargs`);
|
||||
data.value.should.eql({});
|
||||
} finally {
|
||||
await driver.deleteSession();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('cli args handling for passed in args', function () {
|
||||
it('should recieve user cli args from a driver if arguments were passed in', async function () {
|
||||
await serverClose();
|
||||
const args = {driverArgs: FAKE_DRIVER_ARGS};
|
||||
await serverStart(args);
|
||||
let driver = await wdio({...wdOpts, capabilities: caps});
|
||||
const {sessionId} = driver;
|
||||
try {
|
||||
const {data} = await axios.get(`${baseUrl}/${sessionId}/fakedriverargs`);
|
||||
data.value.should.eql(FAKE_ARGS);
|
||||
} finally {
|
||||
await driver.deleteSession();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('session handling', function () {
|
||||
it('should start and stop a session and not allow commands after session stopped', async function () {
|
||||
let driver = await wdio({...wdOpts, capabilities: caps});
|
||||
@@ -292,7 +325,9 @@ describe('FakeDriver - via HTTP', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Logsink', function () {
|
||||
// TODO this test only works if the log has not previously been initialized in the same process.
|
||||
// there seems to be some global state that is not cleaned up between tests.
|
||||
describe.skip('Logsink', function () {
|
||||
let server = null;
|
||||
let logs = [];
|
||||
let logHandler = function (level, message) {
|
||||
|
||||
@@ -5,13 +5,9 @@ import { FakeDriver } from '@appium/fake-driver';
|
||||
import { BASE_CAPS, W3C_CAPS, W3C_PREFIXED_CAPS } from './helpers';
|
||||
import _ from 'lodash';
|
||||
import sinon from 'sinon';
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import { sleep } from 'asyncbox';
|
||||
import { insertAppiumPrefixes, removeAppiumPrefixes } from '../lib/utils';
|
||||
|
||||
chai.should();
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
const SESSION_ID = 1;
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"fake":{
|
||||
"sillyWebServerPort":1234,
|
||||
"host":"hey"
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
import path from 'path';
|
||||
import {insertAppiumPrefixes} from '../lib/utils';
|
||||
import findUp from 'find-up';
|
||||
import getPort from 'get-port';
|
||||
|
||||
const TEST_HOST = 'localhost';
|
||||
|
||||
// monorepo root. this cannot be hardcoded because:
|
||||
// 1. we may be in 'build/test' or 'test', depending on our config
|
||||
// 2. Node.js does not support `__dirname` in an ESM context & Babel doesn't fake it well
|
||||
const PROJECT_ROOT = path.dirname(findUp.sync('.git', {type: 'directory'}));
|
||||
const TEST_FAKE_APP = path.join(PROJECT_ROOT, 'packages', 'fake-driver', 'test', 'fixtures', 'app.xml');
|
||||
const fakeDriverPath = path.dirname(require.resolve('@appium/fake-driver/package.json'));
|
||||
const PROJECT_ROOT = path.join(fakeDriverPath, '..', '..');
|
||||
const TEST_FAKE_APP = path.join(fakeDriverPath, 'test', 'fixtures', 'app.xml');
|
||||
|
||||
const BASE_CAPS = {
|
||||
automationName: 'Fake',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { init as logsinkInit, clear as logsinkClear } from '../lib/logsink';
|
||||
import sinon from 'sinon';
|
||||
import { logger } from 'appium-support';
|
||||
import { logger } from '@appium/support';
|
||||
|
||||
|
||||
// temporarily turn on logging to stdio, so we can catch and query
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// transpile:mocha
|
||||
|
||||
import chai from 'chai';
|
||||
import NPM from '../lib/cli/npm';
|
||||
chai.should();
|
||||
|
||||
describe('getLatestSafeUpgradeFromVersions', function () {
|
||||
const versions1 = ['0.1.0', '0.1.1', '0.2.0', '0.2.5', '1.0.0', '1.0.1', '1.1.5', '1.2.7',
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
import { getParser } from '../lib/cli/parser';
|
||||
import { INSTALL_TYPES, DEFAULT_APPIUM_HOME } from '../lib/extension-config';
|
||||
import chai from 'chai';
|
||||
import path from 'path';
|
||||
|
||||
const should = chai.should();
|
||||
|
||||
const ALLOW_FIXTURE = 'test/fixtures/allow-feat.txt';
|
||||
const DENY_FIXTURE = 'test/fixtures/deny-feat.txt';
|
||||
const FIXTURE_DIR = path.join(__dirname, 'fixtures');
|
||||
const ALLOW_FIXTURE = path.join(FIXTURE_DIR, 'allow-feat.txt');
|
||||
const DENY_FIXTURE = path.join(FIXTURE_DIR, 'deny-feat.txt');
|
||||
const FAKE_DRIVER_ARGS_FIXTURE = path.join(FIXTURE_DIR, 'driverArgs.json');
|
||||
const CAPS_FIXTURE = path.join(FIXTURE_DIR, 'caps.json');
|
||||
|
||||
describe('Main Parser', function () {
|
||||
let p = getParser(true);
|
||||
@@ -48,7 +49,7 @@ describe('Server Parser', function () {
|
||||
});
|
||||
it('should parse default capabilities correctly from a file', function () {
|
||||
let defaultCapabilities = {a: 'b'};
|
||||
let args = p.parse_args(['--default-capabilities', 'test/fixtures/caps.json']);
|
||||
let args = p.parse_args(['--default-capabilities', CAPS_FIXTURE]);
|
||||
args.defaultCapabilities.should.eql(defaultCapabilities);
|
||||
});
|
||||
it('should throw an error with invalid arg to default capabilities', function () {
|
||||
@@ -57,11 +58,6 @@ describe('Server Parser', function () {
|
||||
(() => {p.parse_args(['-dc', 'null']);}).should.throw();
|
||||
(() => {p.parse_args(['-dc', 'does/not/exist.json']);}).should.throw();
|
||||
});
|
||||
it('should parse args that are caps into default capabilities', function () {
|
||||
let defaultCapabilities = {chromedriverExecutable: '/my/dir'};
|
||||
let args = p.parse_args(['--chromedriver-executable', '/my/dir']);
|
||||
args.defaultCapabilities.should.eql(defaultCapabilities);
|
||||
});
|
||||
it('should parse --allow-insecure correctly', function () {
|
||||
p.parse_args([]).allowInsecure.should.eql([]);
|
||||
p.parse_args(['--allow-insecure', '']).allowInsecure.should.eql([]);
|
||||
@@ -83,6 +79,21 @@ describe('Server Parser', function () {
|
||||
parsed.allowInsecure.should.eql(['feature1', 'feature2', 'feature3']);
|
||||
parsed.denyInsecure.should.eql(['nofeature1', 'nofeature2', 'nofeature3']);
|
||||
});
|
||||
it('should parse default driver args correctly from a string', function () {
|
||||
let fakeDriverArgs = {'fake': {'sillyWebServerPort': 1234, 'host': 'hey'}};
|
||||
let args = p.parse_args(['--driver-args', JSON.stringify(fakeDriverArgs)]);
|
||||
args.driverArgs.should.eql(fakeDriverArgs);
|
||||
});
|
||||
it('should parse default driver args correctly from a file', function () {
|
||||
let fakeDriverArgs = {'fake': {'sillyWebServerPort': 1234, 'host': 'hey'}};
|
||||
let args = p.parse_args(['--driver-args', FAKE_DRIVER_ARGS_FIXTURE]);
|
||||
args.driverArgs.should.eql(fakeDriverArgs);
|
||||
});
|
||||
it('should parse default plugin args correctly from a string', function () {
|
||||
let fakePluginArgs = {'fake': {'sillyWebServerPort': 1234, 'host': 'hey'}};
|
||||
let args = p.parse_args(['--plugin-args', JSON.stringify(fakePluginArgs)]);
|
||||
args.pluginArgs.should.eql(fakePluginArgs);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Driver Parser', function () {
|
||||
@@ -190,4 +201,44 @@ describe('Driver Parser', function () {
|
||||
args.appiumHome.should.eql('/foo/bar');
|
||||
});
|
||||
});
|
||||
describe('run', function () {
|
||||
it('should not allow an empty driver argument list', function () {
|
||||
(() => p.parse_args(['driver', 'run'])).should.throw();
|
||||
});
|
||||
it('should not allow no driver scriptName', function () {
|
||||
(() => p.parse_args(['driver', 'run', 'foo'])).should.throw();
|
||||
});
|
||||
it('should take a driverName and scriptName to run', function () {
|
||||
const args = p.parse_args(['driver', 'run', 'foo', 'bar']);
|
||||
args.subcommand.should.eql('driver');
|
||||
args.driverCommand.should.eql('run');
|
||||
args.driver.should.eql('foo');
|
||||
args.scriptName.should.eql('bar');
|
||||
args.appiumHome.should.eql(DEFAULT_APPIUM_HOME);
|
||||
args.json.should.eql(false);
|
||||
});
|
||||
it('should allow json format for driver', function () {
|
||||
const args = p.parse_args(['driver', 'run', 'foo', 'bar', '--json']);
|
||||
args.json.should.eql(true);
|
||||
});
|
||||
it('should not allow an empty plugin argument list', function () {
|
||||
(() => p.parse_args(['plugin', 'run'])).should.throw();
|
||||
});
|
||||
it('should not allow no plugin scriptName', function () {
|
||||
(() => p.parse_args(['plugin', 'run', 'foo'])).should.throw();
|
||||
});
|
||||
it('should take a pluginName and scriptName to run', function () {
|
||||
const args = p.parse_args(['plugin', 'run', 'foo', 'bar']);
|
||||
args.subcommand.should.eql('plugin');
|
||||
args.pluginCommand.should.eql('run');
|
||||
args.plugin.should.eql('foo');
|
||||
args.scriptName.should.eql('bar');
|
||||
args.appiumHome.should.eql(DEFAULT_APPIUM_HOME);
|
||||
args.json.should.eql(false);
|
||||
});
|
||||
it('should allow json format for plugin', function () {
|
||||
const args = p.parse_args(['plugin', 'run', 'foo', 'bar', '--json']);
|
||||
args.json.should.eql(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// transpile:mocha
|
||||
|
||||
import _ from 'lodash';
|
||||
import path from 'path';
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import { remote as wdio } from 'webdriverio';
|
||||
import axios from 'axios';
|
||||
import { main as appiumServer } from '../lib/main';
|
||||
@@ -11,8 +8,9 @@ import { DEFAULT_APPIUM_HOME, INSTALL_TYPE_LOCAL, DRIVER_TYPE, PLUGIN_TYPE } fro
|
||||
import { W3C_PREFIXED_CAPS, TEST_HOST, getTestPort, PROJECT_ROOT } from './helpers';
|
||||
import { runExtensionCommand } from '../lib/cli/extension';
|
||||
|
||||
chai.should();
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
const FAKE_ARGS = {'sillyWebServerPort': 1234, 'host': 'hey'};
|
||||
const FAKE_PLUGIN_ARGS = JSON.stringify({'fake': FAKE_ARGS});
|
||||
|
||||
const wdOpts = {
|
||||
hostname: TEST_HOST,
|
||||
@@ -25,6 +23,7 @@ describe('FakePlugin', function () {
|
||||
const fakePluginDir = path.join(PROJECT_ROOT, 'node_modules', '@appium', 'fake-plugin');
|
||||
const fakeDriverDir = path.join(PROJECT_ROOT, 'packages', 'fake-driver');
|
||||
const appiumHome = DEFAULT_APPIUM_HOME;
|
||||
let baseArgs;
|
||||
let testServer;
|
||||
let testPort;
|
||||
let baseUrl;
|
||||
@@ -61,6 +60,7 @@ describe('FakePlugin', function () {
|
||||
installType: INSTALL_TYPE_LOCAL,
|
||||
}, PLUGIN_TYPE);
|
||||
}
|
||||
baseArgs = {port: testPort, host: TEST_HOST, appiumHome, plugins: ['fake']};
|
||||
});
|
||||
|
||||
describe('without plugin registered', function () {
|
||||
@@ -151,4 +151,51 @@ describe('FakePlugin', function () {
|
||||
});
|
||||
});
|
||||
}
|
||||
describe('cli args handling for plugin args', function () {
|
||||
let server = null;
|
||||
before(async function () {
|
||||
// then start server if we need to
|
||||
const args = {...baseArgs, pluginArgs: FAKE_PLUGIN_ARGS};
|
||||
server = await appiumServer(args);
|
||||
});
|
||||
after(async function () {
|
||||
if (server) {
|
||||
await server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('should recieve user cli args for plugin if passed in', async function () {
|
||||
const driver = await wdio(wdOpts);
|
||||
const {sessionId} = driver;
|
||||
try {
|
||||
const {data} = await axios.get(`${baseUrl}/${sessionId}/fakepluginargs`);
|
||||
data.value.should.eql(FAKE_ARGS);
|
||||
} finally {
|
||||
await driver.deleteSession();
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('cli args handling for empty plugin args', function () {
|
||||
let server = null;
|
||||
before(async function () {
|
||||
// then start server if we need to
|
||||
server = await appiumServer(baseArgs);
|
||||
});
|
||||
after(async function () {
|
||||
if (server) {
|
||||
await server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('should not recieve user cli args for plugin if none were passed in', async function () {
|
||||
const driver = await wdio(wdOpts);
|
||||
const {sessionId} = driver;
|
||||
try {
|
||||
const {data} = await axios.get(`${baseUrl}/${sessionId}/fakepluginargs`);
|
||||
data.value.should.eql({});
|
||||
} finally {
|
||||
await driver.deleteSession();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
// these are extra unit tests to ensure that appium is set up correctly for publishing
|
||||
|
||||
import _ from 'lodash';
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
const expect = chai.expect;
|
||||
|
||||
describe.skip('shrinkwrap checks', function () {
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import {
|
||||
parseCapsForInnerDriver, insertAppiumPrefixes, pullSettings,
|
||||
removeAppiumPrefixes,
|
||||
removeAppiumPrefixes, parseExtensionArgs,
|
||||
} from '../lib/utils';
|
||||
import { BASE_CAPS, W3C_CAPS } from './helpers';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
const should = chai.should();
|
||||
chai.use(chaiAsPromised);
|
||||
const FAKE_ARGS = `{"sillyWebServerPort":1234,"host":"hey"}`;
|
||||
const FAKE_DRIVER_NAME = `fake`;
|
||||
const FAKE_DRIVER_ARGS = `{"${FAKE_DRIVER_NAME}": ${FAKE_ARGS}}`;
|
||||
|
||||
describe('utils', function () {
|
||||
describe('parseCapsForInnerDriver()', function () {
|
||||
@@ -214,4 +212,42 @@ describe('utils', function () {
|
||||
caps.should.eql({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseExtensionArgs()', function () {
|
||||
it('should take a valid json string with a driver name and return a valid json object for that driver', function () {
|
||||
parseExtensionArgs(
|
||||
FAKE_DRIVER_ARGS,
|
||||
FAKE_DRIVER_NAME,
|
||||
).should.eql(JSON.parse(FAKE_DRIVER_ARGS)[FAKE_DRIVER_NAME]);
|
||||
});
|
||||
it('should take a valid json string with an invalid driver name and throw an error', function () {
|
||||
const fakeDriverArgs = `{"xcuitest": ${FAKE_ARGS}}`;
|
||||
(() => parseExtensionArgs(fakeDriverArgs, FAKE_DRIVER_NAME)).should.throw();
|
||||
});
|
||||
it('should take an empty json object and return an empty object', function () {
|
||||
const fakeDriverArgs = {};
|
||||
|
||||
parseExtensionArgs(
|
||||
fakeDriverArgs,
|
||||
FAKE_DRIVER_NAME,
|
||||
).should.eql({});
|
||||
});
|
||||
it('should take an invalid json string and throw an error', function () {
|
||||
const fakeDriverArgs = `blah`;
|
||||
|
||||
(() => parseExtensionArgs(fakeDriverArgs, FAKE_DRIVER_NAME)).should.throw();
|
||||
});
|
||||
it('should take a valid json file with a driver name and return a valid json object for that driver', function () {
|
||||
const fakeDriverArgsPath = require.resolve('./fixtures/driverArgs.json');
|
||||
|
||||
parseExtensionArgs(
|
||||
fakeDriverArgsPath,
|
||||
FAKE_DRIVER_NAME,
|
||||
).should.eql(JSON.parse(FAKE_DRIVER_ARGS)[FAKE_DRIVER_NAME]);
|
||||
});
|
||||
it('should throw an error if arg is not a plain object', function () {
|
||||
const fakeDriverArgs = '{"fake": 1234}';
|
||||
(() => parseExtensionArgs(fakeDriverArgs, FAKE_DRIVER_NAME,)).should.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [8.0.0-beta.7](https://github.com/appium/appium/compare/@appium/base-driver@8.0.0-beta.7...@appium/base-driver@8.0.0-beta.7) (2021-08-16)
|
||||
|
||||
|
||||
|
||||
# 2.0.0-beta (2021-08-13)
|
||||
|
||||
|
||||
### chore
|
||||
|
||||
* update @appium/fake-driver to use @appium/base-driver ([#15436](https://github.com/appium/appium/issues/15436)) ([c66144d](https://github.com/appium/appium/commit/c66144d62b23681f91b45c45648dddf51f0ea991)), closes [#15425](https://github.com/appium/appium/issues/15425)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **appium:** Add driver and plugin server arg injection feature ([#15388](https://github.com/appium/appium/issues/15388)) ([d3c11e3](https://github.com/appium/appium/commit/d3c11e364dffff87ac38ac8dc3ad65a1e4534a9a))
|
||||
* **appium:** expose validateCaps in basedriver index ([#15451](https://github.com/appium/appium/issues/15451)) ([21e8d60](https://github.com/appium/appium/commit/21e8d60a5c768762ebaa7a3232962b0dec385bd0))
|
||||
* **base-driver:** prefer system unzip ([25cc27d](https://github.com/appium/appium/commit/25cc27d161e9425a2ce7420d4417f85d03984921))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* **base-driver:** "chore: merge base-driver master (de7e41b) to base-driver for 2.0" ([#15454](https://github.com/appium/appium/issues/15454)) ([254cc63](https://github.com/appium/appium/commit/254cc638c52063149878866c2abdfe83c5dbee7b))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* `fake-driver` now depends upon `@appium/base-driver@8.x`
|
||||
|
||||
## `@appium/fake-driver`
|
||||
|
||||
- need to use w3c capabilities only
|
||||
- fix: find `app.xml` fixture properly when running tests via `mocha --require=@babel/register`
|
||||
|
||||
## `@appium/base-driver`
|
||||
|
||||
- fixed a dead URL in a comment
|
||||
- updated the "logging" tests to manually supply w3c capabilities. `createSession()` does it for you, but `executeCommand('createSession')` does not.
|
||||
- display the name of the driver under test when executing base driver's test suite with other drivers
|
||||
@@ -53,8 +53,8 @@ export { statusCodes, getSummaryByCode };
|
||||
|
||||
// W3C capabilities parser
|
||||
import * as caps from './lib/basedriver/capabilities';
|
||||
const { processCapabilities, isStandardCap } = caps;
|
||||
export { processCapabilities, isStandardCap };
|
||||
const { processCapabilities, isStandardCap, validateCaps } = caps;
|
||||
export { processCapabilities, isStandardCap, validateCaps };
|
||||
|
||||
// Web socket helpers
|
||||
import * as ws from './lib/express/websocket';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import { validator } from './desired-caps';
|
||||
import { util } from 'appium-support';
|
||||
import { util } from '@appium/support';
|
||||
import log from './logger';
|
||||
import { errors } from '../protocol/errors';
|
||||
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import log from '../logger';
|
||||
import { logger } from 'appium-support';
|
||||
import _ from 'lodash';
|
||||
import { errors } from '../../..';
|
||||
|
||||
|
||||
const commands = {}, helpers = {}, extensions = {};
|
||||
|
||||
const CUSTOM_STRATEGY = '-custom';
|
||||
|
||||
// Override the following function for your own driver, and the rest is taken
|
||||
// care of!
|
||||
@@ -37,18 +33,10 @@ helpers.findElOrElsWithProcessing = async function findElOrElsWithProcessing (st
|
||||
};
|
||||
|
||||
commands.findElement = async function findElement (strategy, selector) {
|
||||
if (strategy === CUSTOM_STRATEGY) {
|
||||
return await this.findByCustom(selector, false);
|
||||
}
|
||||
|
||||
return await this.findElOrElsWithProcessing(strategy, selector, false);
|
||||
};
|
||||
|
||||
commands.findElements = async function findElements (strategy, selector) {
|
||||
if (strategy === CUSTOM_STRATEGY) {
|
||||
return await this.findByCustom(selector, true);
|
||||
}
|
||||
|
||||
return await this.findElOrElsWithProcessing(strategy, selector, true);
|
||||
};
|
||||
|
||||
@@ -60,115 +48,7 @@ commands.findElementsFromElement = async function findElementsFromElement (strat
|
||||
return await this.findElOrElsWithProcessing(strategy, selector, true, elementId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find an element using a custom plugin specified by the customFindModules cap.
|
||||
*
|
||||
* @param {string} selector - the selector which the plugin will use to find
|
||||
* elements
|
||||
* @param {boolean} multiple - whether we want one element or multiple
|
||||
*
|
||||
* @returns {WebElement} - WebDriver element or list of elements
|
||||
*/
|
||||
commands.findByCustom = async function findByCustom (selector, multiple) {
|
||||
const plugins = this.opts.customFindModules;
|
||||
|
||||
// first ensure the user has registered one or more find plugins
|
||||
if (!plugins) {
|
||||
// TODO this info should go in docs instead; update when docs for this
|
||||
// feature exist
|
||||
throw new Error('Finding an element using a plugin is currently an ' +
|
||||
'incubating feature. To use it you must manually install one or more ' +
|
||||
'plugin modules in a way that they can be required by Appium, for ' +
|
||||
'example installing them from the Appium directory, installing them ' +
|
||||
'globally, or installing them elsewhere and passing an absolute path as ' +
|
||||
'the capability. Then construct an object where the key is the shortcut ' +
|
||||
'name for this plugin and the value is the module name or absolute path, ' +
|
||||
'for example: {"p1": "my-find-plugin"}, and pass this in as the ' +
|
||||
"'customFindModules' capability.");
|
||||
}
|
||||
|
||||
// then do some basic checking of the type of the capability
|
||||
if (!_.isPlainObject(plugins)) {
|
||||
throw new Error("Invalid format for the 'customFindModules' capability. " +
|
||||
'It should be an object with keys corresponding to the short names and ' +
|
||||
'values corresponding to the full names of the element finding plugins');
|
||||
}
|
||||
|
||||
// get the name of the particular plugin used for this invocation of find,
|
||||
// and separate it from the selector we will pass to the plugin
|
||||
let [plugin, realSelector] = selector.split(':');
|
||||
|
||||
// if the user didn't specify a plugin for this find invocation, and we had
|
||||
// multiple plugins registered, that's a problem
|
||||
if (_.size(plugins) > 1 && !realSelector) {
|
||||
throw new Error(`Multiple element finding plugins were registered ` +
|
||||
`(${_.keys(plugins)}), but your selector did not indicate which plugin ` +
|
||||
`to use. Ensure you put the short name of the plugin followed by ':' as ` +
|
||||
`the initial part of the selector string.`);
|
||||
}
|
||||
|
||||
// but if they did not specify a plugin and we only have one plugin, just use
|
||||
// that one
|
||||
if (_.size(plugins) === 1 && !realSelector) {
|
||||
realSelector = plugin;
|
||||
plugin = _.keys(plugins)[0];
|
||||
}
|
||||
|
||||
if (!plugins[plugin]) {
|
||||
throw new Error(`Selector specified use of element finding plugin ` +
|
||||
`'${plugin}' but it was not registered in the 'customFindModules' ` +
|
||||
`capability.`);
|
||||
}
|
||||
|
||||
let finder;
|
||||
try {
|
||||
log.debug(`Find plugin '${plugin}' requested; will attempt to use it ` +
|
||||
`from '${plugins[plugin]}'`);
|
||||
finder = require(plugins[plugin]);
|
||||
} catch (err) {
|
||||
throw new Error(`Could not load your custom find module '${plugin}'. Did ` +
|
||||
`you put it somewhere Appium can 'require' it? Original error: ${err}`);
|
||||
}
|
||||
|
||||
if (!finder || !_.isFunction(finder.find)) {
|
||||
throw new Error('Your custom find module did not appear to be constructed ' +
|
||||
'correctly. It needs to export an object with a `find` method.');
|
||||
}
|
||||
|
||||
const customFinderLog = logger.getLogger(plugin);
|
||||
|
||||
let elements;
|
||||
const condition = async () => {
|
||||
// get a list of matched elements from the custom finder, which can
|
||||
// potentially use the entire suite of methods the current driver provides.
|
||||
// the finder should always return a list of elements, but may use the
|
||||
// knowledge of whether we are looking for one or many to perform internal
|
||||
// optimizations
|
||||
elements = await finder.find(this, customFinderLog, realSelector, multiple);
|
||||
|
||||
// if we're looking for multiple elements, or if we're looking for only
|
||||
// one and found it, we're done
|
||||
if (!_.isEmpty(elements) || multiple) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// otherwise we should retry, so return false to trigger the retry loop
|
||||
return false;
|
||||
};
|
||||
|
||||
try {
|
||||
// make sure we respect implicit wait
|
||||
await this.implicitWaitForCondition(condition);
|
||||
} catch (err) {
|
||||
if (err.message.match(/Condition unmet/)) {
|
||||
throw new errors.NoSuchElementError();
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
return multiple ? elements : elements[0];
|
||||
};
|
||||
|
||||
Object.assign(extensions, commands, helpers);
|
||||
export { commands, helpers, CUSTOM_STRATEGY };
|
||||
export { commands, helpers };
|
||||
export default extensions;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import _ from 'lodash';
|
||||
import log from '../logger';
|
||||
import { errors } from '../../protocol';
|
||||
import { util } from 'appium-support';
|
||||
import { util } from '@appium/support';
|
||||
import { processCapabilities } from '../capabilities';
|
||||
|
||||
let commands = {};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import log from '../logger';
|
||||
import { waitForCondition } from 'asyncbox';
|
||||
import _ from 'lodash';
|
||||
import { util } from 'appium-support';
|
||||
import { util } from '@appium/support';
|
||||
import { errors } from '../../protocol';
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
Protocol, errors, determineProtocol
|
||||
} from '../protocol';
|
||||
import { fs } from '@appium/support';
|
||||
import { PROTOCOLS, DEFAULT_BASE_PATH } from '../constants';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import commands from './commands';
|
||||
import * as helpers from './helpers';
|
||||
import log from './logger';
|
||||
@@ -12,16 +12,16 @@ import { desiredCapabilityConstraints } from './desired-caps';
|
||||
import { validateCaps } from './capabilities';
|
||||
import B from 'bluebird';
|
||||
import _ from 'lodash';
|
||||
import { util } from 'appium-support';
|
||||
import AsyncLock from 'async-lock';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
// for compat with running tests transpiled and in-place
|
||||
const {version: BASEDRIVER_VER} = fs.readPackageJsonFrom(__dirname);
|
||||
|
||||
B.config({
|
||||
cancellation: true,
|
||||
});
|
||||
|
||||
const BASEDRIVER_VER = require(path.resolve(__dirname, '..', '..', '..', 'package.json')).version;
|
||||
const NEW_COMMAND_TIMEOUT_MS = 60 * 1000;
|
||||
|
||||
const EVENT_SESSION_INIT = 'newSessionRequested';
|
||||
@@ -38,7 +38,7 @@ class BaseDriver extends Protocol {
|
||||
*/
|
||||
static baseVersion = BASEDRIVER_VER;
|
||||
|
||||
constructor (opts = {}, shouldValidateCaps = true) {
|
||||
constructor (opts = {}, shouldValidateCaps = true, driverArgs = {}) {
|
||||
super();
|
||||
|
||||
// setup state
|
||||
@@ -48,6 +48,9 @@ class BaseDriver extends Protocol {
|
||||
this.originalCaps = null; // To give the original capabilities to reset
|
||||
this.helpers = helpers;
|
||||
|
||||
// allow for cli args to be used by drivers
|
||||
this.driverArgs = driverArgs;
|
||||
|
||||
// basePath is used for several purposes, for example in setting up
|
||||
// proxying to other drivers, since we need to know what the base path
|
||||
// of any incoming request might look like. We set it to the default
|
||||
@@ -421,34 +424,6 @@ class BaseDriver extends Protocol {
|
||||
this.clearNewCommandTimeout();
|
||||
}
|
||||
|
||||
async getSwipeOptions (gestures, touchCount = 1) {
|
||||
let startX = this.helpers.getCoordDefault(gestures[0].options.x),
|
||||
startY = this.helpers.getCoordDefault(gestures[0].options.y),
|
||||
endX = this.helpers.getCoordDefault(gestures[2].options.x),
|
||||
endY = this.helpers.getCoordDefault(gestures[2].options.y),
|
||||
duration = this.helpers.getSwipeTouchDuration(gestures[1]),
|
||||
element = gestures[0].options.element,
|
||||
destElement = gestures[2].options.element || gestures[0].options.element;
|
||||
|
||||
// there's no destination element handling in bootstrap and since it applies to all platforms, we handle it here
|
||||
if (util.hasValue(destElement)) {
|
||||
let locResult = await this.getLocationInView(destElement);
|
||||
let sizeResult = await this.getSize(destElement);
|
||||
let offsetX = (Math.abs(endX) < 1 && Math.abs(endX) > 0) ? sizeResult.width * endX : endX;
|
||||
let offsetY = (Math.abs(endY) < 1 && Math.abs(endY) > 0) ? sizeResult.height * endY : endY;
|
||||
endX = locResult.x + offsetX;
|
||||
endY = locResult.y + offsetY;
|
||||
// if the target element was provided, the coordinates for the destination need to be relative to it.
|
||||
if (util.hasValue(element)) {
|
||||
let firstElLocation = await this.getLocationInView(element);
|
||||
endX -= firstElLocation.x;
|
||||
endY -= firstElLocation.y;
|
||||
}
|
||||
}
|
||||
// clients are responsible to use these options correctly
|
||||
return {startX, startY, endX, endY, duration, touchCount, element};
|
||||
}
|
||||
|
||||
proxyActive (/* sessionId */) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,11 @@ import _ from 'lodash';
|
||||
import path from 'path';
|
||||
import url from 'url';
|
||||
import logger from './logger';
|
||||
import { tempDir, fs, util, zip, net, timing } from 'appium-support';
|
||||
import { system, tempDir, fs, util, zip, net, timing } from '@appium/support';
|
||||
import LRU from 'lru-cache';
|
||||
import AsyncLock from 'async-lock';
|
||||
import axios from 'axios';
|
||||
|
||||
|
||||
const IPA_EXT = '.ipa';
|
||||
const ZIP_EXTS = ['.zip', IPA_EXT];
|
||||
const ZIP_MIME_TYPES = [
|
||||
@@ -323,7 +322,15 @@ async function unzipApp (zipPath, dstRoot, supportedAppExtensions) {
|
||||
try {
|
||||
logger.debug(`Unzipping '${zipPath}'`);
|
||||
const timer = new timing.Timer().start();
|
||||
const extractionOpts = {};
|
||||
/**
|
||||
* Attempt to use use the system `unzip` (e.g., `/usr/bin/unzip`) due
|
||||
* to the significant performance improvement it provides over the native
|
||||
* JS "unzip" implementation.
|
||||
* @type {import('@appium/support/lib/zip').ExtractAllOptions}
|
||||
*/
|
||||
const extractionOpts = {
|
||||
useSystemUnzip: !system.isWindows(),
|
||||
};
|
||||
// https://github.com/appium/appium/issues/14100
|
||||
if (path.extname(zipPath) === IPA_EXT) {
|
||||
logger.debug(`Enforcing UTF-8 encoding on the extracted file names for '${path.basename(zipPath)}'`);
|
||||
@@ -359,28 +366,6 @@ function isPackageOrBundle (app) {
|
||||
return (/^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)+$/).test(app);
|
||||
}
|
||||
|
||||
function getCoordDefault (val) {
|
||||
// going the long way and checking for undefined and null since
|
||||
// we can't be assured `elId` is a string and not an int. Same
|
||||
// thing with destElement below.
|
||||
return util.hasValue(val) ? val : 0.5;
|
||||
}
|
||||
|
||||
function getSwipeTouchDuration (waitGesture) {
|
||||
// the touch action api uses ms, we want seconds
|
||||
// 0.8 is the default time for the operation
|
||||
let duration = 0.8;
|
||||
if (typeof waitGesture.options.ms !== 'undefined' && waitGesture.options.ms) {
|
||||
duration = waitGesture.options.ms / 1000;
|
||||
if (duration === 0) {
|
||||
// set to a very low number, since they wanted it fast
|
||||
// but below 0.1 becomes 0 steps, which causes errors
|
||||
duration = 0.1;
|
||||
}
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all instances 'firstKey' and create a duplicate with the key 'secondKey',
|
||||
* Do the same thing in reverse. If we find 'secondKey', create a duplicate with the key 'firstKey'.
|
||||
@@ -443,5 +428,5 @@ function parseCapsArray (cap) {
|
||||
}
|
||||
|
||||
export {
|
||||
configureApp, isPackageOrBundle, getCoordDefault, getSwipeTouchDuration, duplicateKeys, parseCapsArray
|
||||
configureApp, isPackageOrBundle, duplicateKeys, parseCapsArray
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { logger } from 'appium-support';
|
||||
import { logger } from '@appium/support';
|
||||
|
||||
const log = logger.getLogger('BaseDriver');
|
||||
export default log;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { util } from 'appium-support';
|
||||
import { util } from '@appium/support';
|
||||
|
||||
// The default maximum length of a single log record
|
||||
// containing http request/response body
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import log from './logger';
|
||||
import LRU from 'lru-cache';
|
||||
import { fs, util } from 'appium-support';
|
||||
import { fs, util } from '@appium/support';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { logger } from 'appium-support';
|
||||
import { logger } from '@appium/support';
|
||||
|
||||
const log = logger.getLogger('HTTP');
|
||||
export default log;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from 'path';
|
||||
import log from './logger';
|
||||
import _ from 'lodash';
|
||||
import { fs } from 'appium-support';
|
||||
import { fs } from '@appium/support';
|
||||
import B from 'bluebird';
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import { logger, util } from 'appium-support';
|
||||
import { logger, util } from '@appium/support';
|
||||
import { duplicateKeys } from '../basedriver/helpers';
|
||||
import {
|
||||
MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, PROTOCOLS
|
||||
@@ -190,6 +190,10 @@ class ProtocolConverter {
|
||||
: await this.proxyFunc(url, method, body);
|
||||
}
|
||||
|
||||
async proxyReleaseActions (url, method) {
|
||||
return await this.proxyFunc(url, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "crossing" endpoints for the case
|
||||
* when upstream and downstream drivers operate different protocols
|
||||
@@ -218,6 +222,8 @@ class ProtocolConverter {
|
||||
return await this.proxySetValue(url, method, body);
|
||||
case 'performActions':
|
||||
return await this.proxyPerformActions(url, method, body);
|
||||
case 'releaseActions':
|
||||
return await this.proxyReleaseActions(url, method, body);
|
||||
case 'setFrame':
|
||||
return await this.proxySetFrame(url, method, body);
|
||||
default:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import { logger, util } from 'appium-support';
|
||||
import { logger, util } from '@appium/support';
|
||||
import axios from 'axios';
|
||||
import { getSummaryByCode } from '../jsonwp-status/status';
|
||||
import {
|
||||
@@ -41,7 +41,6 @@ class JWProxy {
|
||||
keepAlive: opts.keepAlive ?? true,
|
||||
maxSockets: 10,
|
||||
maxFreeSockets: 5,
|
||||
timeout: 60000,
|
||||
};
|
||||
this.httpAgent = new http.Agent(agentOpts);
|
||||
this.httpsAgent = new https.Agent(agentOpts);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import ES6Error from 'es6-error';
|
||||
import _ from 'lodash';
|
||||
import { util, logger } from 'appium-support';
|
||||
import { util, logger } from '@appium/support';
|
||||
import { StatusCodes as HTTPStatusCodes } from 'http-status-codes';
|
||||
|
||||
const mjsonwpLog = logger.getLogger('MJSONWP');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import { util } from 'appium-support';
|
||||
import { util } from '@appium/support';
|
||||
import { validators } from './validators';
|
||||
import {
|
||||
errors, isErrorType, getResponseForW3CError, getResponseForJsonwpError,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import { util } from 'appium-support';
|
||||
import { util } from '@appium/support';
|
||||
import { PROTOCOLS, DEFAULT_BASE_PATH } from '../constants';
|
||||
|
||||
|
||||
@@ -280,6 +280,7 @@ const METHOD_MAP = {
|
||||
},
|
||||
'/session/:sessionId/actions': {
|
||||
POST: {command: 'performActions', payloadParams: {required: ['actions']}},
|
||||
DELETE: {command: 'releaseActions'}
|
||||
},
|
||||
'/session/:sessionId/touch/longclick': {
|
||||
POST: {command: 'touchLongClick', payloadParams: {required: ['elements']}}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import LRU from 'lru-cache';
|
||||
import { logger } from 'appium-support';
|
||||
import { logger } from '@appium/support';
|
||||
import { PROTOCOLS } from '../constants';
|
||||
|
||||
|
||||
|
||||
@@ -2,10 +2,17 @@
|
||||
"name": "@appium/base-driver",
|
||||
"description": "Base driver class for Appium drivers",
|
||||
"keywords": [
|
||||
"appium"
|
||||
"automation",
|
||||
"javascript",
|
||||
"selenium",
|
||||
"webdriver",
|
||||
"ios",
|
||||
"android",
|
||||
"firefoxos",
|
||||
"testing"
|
||||
],
|
||||
"version": "8.0.0-beta.6",
|
||||
"author": "appium",
|
||||
"version": "8.0.0-beta.7",
|
||||
"author": "https://github.com/appium",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -15,7 +22,8 @@
|
||||
"url": "https://github.com/appium/appium/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
},
|
||||
"main": "./build/index.js",
|
||||
"directories": {
|
||||
@@ -33,29 +41,34 @@
|
||||
"!build/test/basedriver/fixtures"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"@appium/support": "^2.48.0",
|
||||
"async-lock": "^1.0.0",
|
||||
"asyncbox": "^2.3.1",
|
||||
"axios": "^0.21.0",
|
||||
"@appium/support": "^2.54.0",
|
||||
"@babel/runtime": "^7.15.3",
|
||||
"async-lock": "^1.3.0",
|
||||
"asyncbox": "^2.8.0",
|
||||
"axios": "^0.x",
|
||||
"bluebird": "^3.5.3",
|
||||
"body-parser": "^1.18.2",
|
||||
"colors": "^1.1.2",
|
||||
"body-parser": "^1.19.0",
|
||||
"colors": "^1.4.0",
|
||||
"es6-error": "^4.1.1",
|
||||
"express": "^4.16.2",
|
||||
"http-status-codes": "^2.1.1",
|
||||
"lodash": "^4.0.0",
|
||||
"express": "^4.17.1",
|
||||
"http-status-codes": "^2.1.4",
|
||||
"lodash": "^4.17.21",
|
||||
"lru-cache": "^6.0.0",
|
||||
"method-override": "^3.0.0",
|
||||
"morgan": "^1.9.0",
|
||||
"serve-favicon": "^2.4.5",
|
||||
"source-map-support": "^0.5.5",
|
||||
"morgan": "^1.10.0",
|
||||
"serve-favicon": "^2.5.0",
|
||||
"source-map-support": "^0.5",
|
||||
"validate.js": "^0.13.0",
|
||||
"webdriverio": "^6.0.2",
|
||||
"ws": "^7.0.0"
|
||||
},
|
||||
"webdriverio": "^6.12.1",
|
||||
"ws": "^7.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appium/gulp-plugins": "^5.4.0",
|
||||
"@appium/eslint-config-appium": "^4.7.0"
|
||||
}
|
||||
"@appium/eslint-config-appium": "^4.7.1",
|
||||
"@appium/gulp-plugins": "^5.5.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"tag": "beta"
|
||||
},
|
||||
"homepage": "https://appium.io"
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { parseCaps, validateCaps, mergeCaps, processCapabilities, findNonPrefixedCaps } from '../../lib/basedriver/capabilities';
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import _ from 'lodash';
|
||||
import { desiredCapabilityConstraints } from '../../lib/basedriver/desired-caps';
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
const should = chai.should();
|
||||
|
||||
describe('caps', function () {
|
||||
|
||||
@@ -99,7 +95,7 @@ describe('caps', function () {
|
||||
});
|
||||
});
|
||||
|
||||
// Tests based on: https://www.w3.org/TR/webdriver/#processing-caps
|
||||
// Tests based on: https://www.w3.org/TR/webdriver/#processing-capabilities
|
||||
describe('#parseCaps', function () {
|
||||
let caps;
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { default as BaseDriver, errors } from '../..';
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import logger from '../../lib/basedriver/logger';
|
||||
import sinon from 'sinon';
|
||||
|
||||
const should = chai.should();
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
describe('Desired Capabilities', function () {
|
||||
let d;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import { BaseDriver } from '../../..';
|
||||
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
describe('logging custom events', function () {
|
||||
it('should allow logging of events', async function () {
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import chai from 'chai';
|
||||
import path from 'path';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import { BaseDriver } from '../../..';
|
||||
import { CUSTOM_STRATEGY } from '../../../lib/basedriver/commands/find';
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
const CUSTOM_FIND_MODULE = path.resolve(__dirname, '..', '..', '..', '..',
|
||||
'test', 'basedriver', 'fixtures', 'custom-element-finder');
|
||||
const BAD_CUSTOM_FIND_MODULE = path.resolve(__dirname, '..', '..', '..', '..',
|
||||
'test', 'basedriver', 'fixtures', 'custom-element-finder-bad');
|
||||
|
||||
describe('custom element finding plugins', function () {
|
||||
// happys
|
||||
it('should find a single element using a custom finder', async function () {
|
||||
const d = new BaseDriver();
|
||||
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE};
|
||||
await d.findElement(CUSTOM_STRATEGY, 'f:foo').should.eventually.eql('bar');
|
||||
});
|
||||
it('should not require selector prefix if only one find plugin is registered', async function () {
|
||||
const d = new BaseDriver();
|
||||
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE};
|
||||
await d.findElement(CUSTOM_STRATEGY, 'foo').should.eventually.eql('bar');
|
||||
});
|
||||
it('should find multiple elements using a custom finder', async function () {
|
||||
const d = new BaseDriver();
|
||||
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE};
|
||||
await d.findElements(CUSTOM_STRATEGY, 'f:foos').should.eventually.eql(['baz1', 'baz2']);
|
||||
});
|
||||
it('should give a hint to the plugin about whether multiple are requested', async function () {
|
||||
const d = new BaseDriver();
|
||||
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE};
|
||||
await d.findElement(CUSTOM_STRATEGY, 'f:foos').should.eventually.eql('bar1');
|
||||
});
|
||||
it('should be able to use multiple find modules', async function () {
|
||||
const d = new BaseDriver();
|
||||
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE, g: CUSTOM_FIND_MODULE};
|
||||
await d.findElement(CUSTOM_STRATEGY, 'f:foo').should.eventually.eql('bar');
|
||||
await d.findElement(CUSTOM_STRATEGY, 'g:foo').should.eventually.eql('bar');
|
||||
});
|
||||
|
||||
// errors
|
||||
it('should throw an error if customFindModules is not set', async function () {
|
||||
const d = new BaseDriver();
|
||||
await d.findElement(CUSTOM_STRATEGY, 'f:foo').should.eventually.be.rejectedWith(/customFindModules/);
|
||||
});
|
||||
it('should throw an error if customFindModules is the wrong shape', async function () {
|
||||
const d = new BaseDriver();
|
||||
d.opts.customFindModules = CUSTOM_FIND_MODULE;
|
||||
await d.findElement(CUSTOM_STRATEGY, 'f:foo').should.eventually.be.rejectedWith(/customFindModules/);
|
||||
});
|
||||
it('should throw an error if customFindModules is size > 1 and no selector prefix is used', async function () {
|
||||
const d = new BaseDriver();
|
||||
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE, g: CUSTOM_FIND_MODULE};
|
||||
await d.findElement(CUSTOM_STRATEGY, 'foo').should.eventually.be.rejectedWith(/multiple element finding/i);
|
||||
});
|
||||
it('should throw an error in attempt to use unregistered plugin', async function () {
|
||||
const d = new BaseDriver();
|
||||
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE, g: CUSTOM_FIND_MODULE};
|
||||
await d.findElement(CUSTOM_STRATEGY, 'z:foo').should.eventually.be.rejectedWith(/was not registered/);
|
||||
});
|
||||
it('should throw an error if plugin cannot be loaded', async function () {
|
||||
const d = new BaseDriver();
|
||||
d.opts.customFindModules = {f: './foo.js'};
|
||||
await d.findElement(CUSTOM_STRATEGY, 'f:foo').should.eventually.be.rejectedWith(/could not load/i);
|
||||
});
|
||||
it('should throw an error if plugin is not the right shape', async function () {
|
||||
const d = new BaseDriver();
|
||||
d.opts.customFindModules = {f: BAD_CUSTOM_FIND_MODULE};
|
||||
await d.findElement(CUSTOM_STRATEGY, 'f:foo').should.eventually.be.rejectedWith(/constructed correctly/i);
|
||||
});
|
||||
it('should pass on an error thrown by the finder itself', async function () {
|
||||
const d = new BaseDriver();
|
||||
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE};
|
||||
await d.findElement(CUSTOM_STRATEGY, 'f:error').should.eventually.be.rejectedWith(/plugin error/i);
|
||||
});
|
||||
it('should throw no such element error if element not found', async function () {
|
||||
const d = new BaseDriver();
|
||||
d.opts.customFindModules = {f: CUSTOM_FIND_MODULE};
|
||||
await d.findElement(CUSTOM_STRATEGY, 'f:nope').should.eventually.be.rejectedWith(/could not be located/);
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,8 @@
|
||||
import logCommands from '../../../lib/basedriver/commands/log';
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import sinon from 'sinon';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
chai.should();
|
||||
chai.use(chaiAsPromised);
|
||||
const expect = chai.expect;
|
||||
|
||||
const FIRST_LOGS = ['first', 'logs'];
|
||||
@@ -71,4 +67,4 @@ describe('log commands -', function () {
|
||||
SUPPORTED_LOG_TYPES.two.getter.called.should.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,26 +4,20 @@ import {
|
||||
MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY
|
||||
} from '../../lib/constants';
|
||||
import axios from 'axios';
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import B from 'bluebird';
|
||||
import getPort from 'get-port';
|
||||
|
||||
const should = chai.should();
|
||||
const DEFAULT_ARGS = {
|
||||
address: 'localhost',
|
||||
port: null
|
||||
};
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
let port;
|
||||
describe('BaseDriver (e2e)', function () {
|
||||
let address = defaultCaps['appium:address'] ?? '127.0.0.1';
|
||||
let port = defaultCaps['appium:port'];
|
||||
const className = DriverClass.name || '(unknown driver)';
|
||||
|
||||
describe(`BaseDriver E2E (as ${className})`, function () {
|
||||
let baseServer, d;
|
||||
before(async function () {
|
||||
port = await getPort();
|
||||
DEFAULT_ARGS.port = port;
|
||||
d = new DriverClass(DEFAULT_ARGS);
|
||||
port = port ?? await getPort();
|
||||
defaultCaps = {...defaultCaps, 'appium:port': port};
|
||||
d = new DriverClass({port, address});
|
||||
baseServer = await server({
|
||||
routeConfiguringFunction: routeConfiguringFunction(d),
|
||||
port
|
||||
@@ -35,7 +29,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
|
||||
async function startSession (caps) {
|
||||
return (await axios({
|
||||
url: `http://localhost:${port}/session`,
|
||||
url: `http://${address}:${port}/session`,
|
||||
method: 'POST',
|
||||
data: {capabilities: {alwaysMatch: caps, firstMatch: [{}]}},
|
||||
})).data.value;
|
||||
@@ -43,7 +37,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
|
||||
async function endSession (id) {
|
||||
return (await axios({
|
||||
url: `http://localhost:${port}/session/${id}`,
|
||||
url: `http://${address}:${port}/session/${id}`,
|
||||
method: 'DELETE',
|
||||
validateStatus: null,
|
||||
})).data.value;
|
||||
@@ -51,7 +45,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
|
||||
async function getSession (id) {
|
||||
return (await axios({
|
||||
url: `http://localhost:${port}/session/${id}`,
|
||||
url: `http://${address}:${port}/session/${id}`,
|
||||
})).data.value;
|
||||
}
|
||||
|
||||
@@ -61,7 +55,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
let times = 0;
|
||||
do {
|
||||
const {sessionId} = (await axios({
|
||||
url: `http://localhost:${port}/session`,
|
||||
url: `http://${address}:${port}/session`,
|
||||
headers: {
|
||||
'X-Idempotency-Key': '123456',
|
||||
},
|
||||
@@ -77,7 +71,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
_.uniq(sessionIds).length.should.equal(1);
|
||||
|
||||
const {status, data} = await axios({
|
||||
url: `http://localhost:${port}/session/${sessionIds[0]}`,
|
||||
url: `http://${address}:${port}/session/${sessionIds[0]}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
status.should.equal(200);
|
||||
@@ -89,7 +83,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
let times = 0;
|
||||
do {
|
||||
reqs.push(axios({
|
||||
url: `http://localhost:${port}/session`,
|
||||
url: `http://${address}:${port}/session`,
|
||||
headers: {
|
||||
'X-Idempotency-Key': '12345',
|
||||
},
|
||||
@@ -102,7 +96,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
_.uniq(sessionIds).length.should.equal(1);
|
||||
|
||||
const {status, data} = await axios({
|
||||
url: `http://localhost:${port}/session/${sessionIds[0]}`,
|
||||
url: `http://${address}:${port}/session/${sessionIds[0]}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
status.should.equal(200);
|
||||
@@ -111,18 +105,18 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
|
||||
it('should create session and retrieve a session id, then delete it', async function () {
|
||||
let {status, data} = await axios({
|
||||
url: `http://localhost:${port}/session`,
|
||||
url: `http://${address}:${port}/session`,
|
||||
method: 'POST',
|
||||
data: {capabilities: {alwaysMatch: defaultCaps, firstMatch: [{}]}},
|
||||
});
|
||||
|
||||
status.should.equal(200);
|
||||
should.exist(data.value.sessionId);
|
||||
data.value.capabilities.platformName.should.equal('iOS');
|
||||
data.value.capabilities.deviceName.should.equal('Delorean');
|
||||
data.value.capabilities.platformName.should.equal(defaultCaps.platformName);
|
||||
data.value.capabilities.deviceName.should.equal(defaultCaps['appium:deviceName']);
|
||||
|
||||
({status, data} = await axios({
|
||||
url: `http://localhost:${port}/session/${d.sessionId}`,
|
||||
url: `http://${address}:${port}/session/${d.sessionId}`,
|
||||
method: 'DELETE',
|
||||
}));
|
||||
|
||||
@@ -172,13 +166,13 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
let newSession = await startTimeoutSession(0.25);
|
||||
|
||||
await axios({
|
||||
url: `http://localhost:${port}/session/${d.sessionId}/element`,
|
||||
url: `http://${address}:${port}/session/${d.sessionId}/element`,
|
||||
method: 'POST',
|
||||
data: {using: 'name', value: 'foo'},
|
||||
});
|
||||
await B.delay(400);
|
||||
const {data} = await axios({
|
||||
url: `http://localhost:${port}/session/${d.sessionId}`,
|
||||
url: `http://${address}:${port}/session/${d.sessionId}`,
|
||||
validateStatus: null,
|
||||
});
|
||||
should.equal(data.value.error, 'invalid session id');
|
||||
@@ -191,7 +185,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
let newSession = await startTimeoutSession(0.1);
|
||||
let start = Date.now();
|
||||
const {value} = (await axios({
|
||||
url: `http://localhost:${port}/session/${d.sessionId}/elements`,
|
||||
url: `http://${address}:${port}/session/${d.sessionId}/elements`,
|
||||
method: 'POST',
|
||||
data: {using: 'name', value: 'foo'},
|
||||
})).data;
|
||||
@@ -205,15 +199,15 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
let newSession = await startTimeoutSession(0);
|
||||
|
||||
await axios({
|
||||
url: `http://localhost:${port}/session/${d.sessionId}/element`,
|
||||
url: `http://${address}:${port}/session/${d.sessionId}/element`,
|
||||
method: 'POST',
|
||||
data: {using: 'name', value: 'foo'},
|
||||
});
|
||||
await B.delay(400);
|
||||
const {value} = (await axios({
|
||||
url: `http://localhost:${port}/session/${d.sessionId}`,
|
||||
url: `http://${address}:${port}/session/${d.sessionId}`,
|
||||
})).data;
|
||||
value.platformName.should.equal('iOS');
|
||||
value.platformName.should.equal(defaultCaps.platformName);
|
||||
const resp = await endSession(newSession.sessionId);
|
||||
should.equal(resp, null);
|
||||
|
||||
@@ -223,13 +217,13 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
it('should not timeout if its just the command taking awhile', async function () {
|
||||
let newSession = await startTimeoutSession(0.25);
|
||||
await axios({
|
||||
url: `http://localhost:${port}/session/${d.sessionId}/element`,
|
||||
url: `http://${address}:${port}/session/${d.sessionId}/element`,
|
||||
method: 'POST',
|
||||
data: {using: 'name', value: 'foo'},
|
||||
});
|
||||
await B.delay(400);
|
||||
const {value} = (await axios({
|
||||
url: `http://localhost:${port}/session/${d.sessionId}`,
|
||||
url: `http://${address}:${port}/session/${d.sessionId}`,
|
||||
validateStatus: null,
|
||||
})).data;
|
||||
value.error.should.equal('invalid session id');
|
||||
@@ -274,7 +268,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
await B.delay(5000);
|
||||
}.bind(d);
|
||||
const reqPromise = axios({
|
||||
url: `http://localhost:${port}/status`,
|
||||
url: `http://${address}:${port}/status`,
|
||||
validateStatus: null,
|
||||
});
|
||||
// make sure that the request gets to the server before our shutdown
|
||||
@@ -347,7 +341,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
d.allowInsecure = [];
|
||||
const script = `return 'foo'`;
|
||||
await axios({
|
||||
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
method: 'POST',
|
||||
data: {script, type: 'wd'},
|
||||
}).should.eventually.be.rejected;
|
||||
@@ -364,7 +358,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
return [timeouts, status];
|
||||
`;
|
||||
const {value} = (await axios({
|
||||
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
method: 'POST',
|
||||
data: {script, type: 'webdriverio'},
|
||||
})).data;
|
||||
@@ -376,7 +370,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
it('should fail with any script type other than webdriverio currently', async function () {
|
||||
const script = `return 'foo'`;
|
||||
await axios({
|
||||
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
method: 'POST',
|
||||
data: {script, type: 'wd'},
|
||||
}).should.eventually.be.rejected;
|
||||
@@ -387,7 +381,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
return await driver.$("~amazing");
|
||||
`;
|
||||
const {value} = (await axios({
|
||||
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
method: 'POST',
|
||||
data: {script},
|
||||
})).data;
|
||||
@@ -403,7 +397,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
return {element: el, elements: [el, el]};
|
||||
`;
|
||||
const {value} = (await axios({
|
||||
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
method: 'POST',
|
||||
data: {script},
|
||||
})).data;
|
||||
@@ -423,7 +417,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
return null;
|
||||
`;
|
||||
const {value} = (await axios({
|
||||
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
method: 'POST',
|
||||
data: {script},
|
||||
})).data;
|
||||
@@ -435,7 +429,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
return typeof driver.lock;
|
||||
`;
|
||||
const {value} = (await axios({
|
||||
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
method: 'POST',
|
||||
data: {script},
|
||||
})).data;
|
||||
@@ -447,7 +441,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
return await driver.$("~notfound");
|
||||
`;
|
||||
const {data} = await axios({
|
||||
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
method: 'POST',
|
||||
validateStatus: null,
|
||||
data: {script},
|
||||
@@ -464,7 +458,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
return {;
|
||||
`;
|
||||
const {data} = await axios({
|
||||
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
method: 'POST',
|
||||
validateStatus: null,
|
||||
data: {script},
|
||||
@@ -479,7 +473,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
|
||||
return true;
|
||||
`;
|
||||
const {value} = (await axios({
|
||||
url: `http://localhost:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
url: `http://${address}:${port}/session/${sessionId}/appium/execute_driver`,
|
||||
method: 'POST',
|
||||
validateStatus: null,
|
||||
data: {script, timeout: 50},
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import _ from 'lodash';
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import B from 'bluebird';
|
||||
import { DeviceSettings } from '../..';
|
||||
import sinon from 'sinon';
|
||||
|
||||
|
||||
const should = chai.should();
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
// wrap these tests in a function so we can export the tests and re-use them
|
||||
// for actual driver implementations
|
||||
function baseDriverUnitTests (DriverClass, defaultCaps = {}) {
|
||||
describe('BaseDriver', function () {
|
||||
// to display the driver under test in report
|
||||
const className = DriverClass.name || '(unknown driver)';
|
||||
|
||||
describe(`BaseDriver (as ${className})`, function () {
|
||||
let d, w3cCaps;
|
||||
|
||||
beforeEach(function () {
|
||||
@@ -75,12 +73,12 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) {
|
||||
let sessions = await d.getSessions();
|
||||
|
||||
sessions.length.should.equal(1);
|
||||
sessions[0].should.eql({
|
||||
id: d.sessionId,
|
||||
capabilities: {
|
||||
'deviceName': 'Commodore 64',
|
||||
'platformName': 'Fake'
|
||||
}
|
||||
sessions[0].should.include({
|
||||
id: d.sessionId
|
||||
});
|
||||
sessions[0].capabilities.should.include({
|
||||
deviceName: 'Commodore 64',
|
||||
platformName: 'Fake'
|
||||
});
|
||||
});
|
||||
|
||||
@@ -393,7 +391,7 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) {
|
||||
beforeEach(async function () {
|
||||
beforeStartTime = Date.now();
|
||||
d.shouldValidateCaps = false;
|
||||
await d.executeCommand('createSession', null, null, defaultCaps);
|
||||
await d.executeCommand('createSession', null, null, {alwaysMatch: {...defaultCaps}, firstMatch: [{}]});
|
||||
});
|
||||
describe('#eventHistory', function () {
|
||||
it('should have an eventHistory property', function () {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import chai from 'chai';
|
||||
import path from 'path';
|
||||
import url from 'url';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import { fs } from 'appium-support';
|
||||
import { fs } from '@appium/support';
|
||||
import { configureApp } from '../../lib/basedriver/helpers';
|
||||
import http from 'http';
|
||||
import finalhandler from 'finalhandler';
|
||||
@@ -11,8 +9,6 @@ import contentDisposition from 'content-disposition';
|
||||
import B from 'bluebird';
|
||||
|
||||
|
||||
chai.should();
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
function getFixture (file) {
|
||||
// XXX: __dirname disallowed in native ESM
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { isPackageOrBundle, duplicateKeys, parseCapsArray } from '../../lib/basedriver/helpers';
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import { zip, fs, tempDir } from '@appium/support';
|
||||
import { configureApp, isPackageOrBundle, duplicateKeys, parseCapsArray } from '../../lib/basedriver/helpers';
|
||||
import sinon from 'sinon';
|
||||
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
const should = chai.should();
|
||||
|
||||
describe('helpers', function () {
|
||||
describe('#isPackageOrBundle', function () {
|
||||
it('should accept packages and bundles', function () {
|
||||
@@ -92,6 +89,31 @@ describe('helpers', function () {
|
||||
duplicateKeys(input, 'foo', 'FOO').should.deep.equal(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#configureApp', function () {
|
||||
let sandbox;
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = sinon.createSandbox();
|
||||
sandbox.stub(zip, 'extractAllTo').resolves();
|
||||
sandbox.stub(zip, 'assertValidZip').resolves();
|
||||
sandbox.stub(fs, 'mv').resolves();
|
||||
sandbox.stub(fs, 'exists').resolves(true);
|
||||
sandbox.stub(fs, 'hash').resolves('0xDEADBEEF');
|
||||
sandbox.stub(fs, 'glob').resolves(['/path/to/an.apk']);
|
||||
sandbox.stub(fs, 'rimraf').resolves();
|
||||
sandbox.stub(tempDir, 'openDir').resolves('/some/dir');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('should pass "useSystemUnzip" flag through to @appium/support', async function () {
|
||||
await configureApp('/path/to/an.apk.zip', '.apk');
|
||||
zip.extractAllTo.getCall(0).lastArg.useSystemUnzip.should.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseCapsArray', function () {
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import BaseDriver from '../..';
|
||||
import sinon from 'sinon';
|
||||
|
||||
|
||||
chai.should();
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
|
||||
describe('timeout', function () {
|
||||
|
||||
@@ -2,13 +2,10 @@ import _ from 'lodash';
|
||||
import { server, routeConfiguringFunction,
|
||||
DEFAULT_WS_PATHNAME_PREFIX } from '../..';
|
||||
import { FakeDriver } from '../protocol/fake-driver';
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import WebSocket from 'ws';
|
||||
import B from 'bluebird';
|
||||
import getPort from 'get-port';
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
describe('Websockets (e2e)', function () {
|
||||
let baseServer;
|
||||
|
||||
@@ -2,15 +2,11 @@
|
||||
|
||||
import { server } from '../..';
|
||||
import axios from 'axios';
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import sinon from 'sinon';
|
||||
import B from 'bluebird';
|
||||
import _ from 'lodash';
|
||||
import getPort from 'get-port';
|
||||
|
||||
chai.should();
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
describe('server', function () {
|
||||
let hwServer;
|
||||
|
||||
@@ -2,14 +2,9 @@
|
||||
|
||||
import { server, routeConfiguringFunction } from '../../index.js';
|
||||
import { configureServer, normalizeBasePath } from '../../lib/express/server';
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import sinon from 'sinon';
|
||||
import getPort from 'get-port';
|
||||
|
||||
const should = chai.should();
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
function fakeApp () {
|
||||
const app = {
|
||||
use: sinon.spy(),
|
||||
|
||||