docs(appium): Update documentation translations (#22927)

Co-authored-by: mykola-mokhnach <7767781+mykola-mokhnach@users.noreply.github.com>
This commit is contained in:
github-actions[bot]
2026-10-09 13:01:09 +03:00
committed by GitHub
parent 0dc96de9a9
commit 6f68dc9093
22 changed files with 292 additions and 442 deletions
@@ -7,7 +7,7 @@ date: 2024-07-13
# Appium Conf 2024: 现在开始征集提案
我们很兴奋地宣布,Appium Conf 已回归并在2024年上线! 这是官方的Appium会议,您可以在会上了解Appium 2中的所有新内容。 并探索设备自动化测试、移动应用程序和桌面应用程序的最新版本。
我们很兴奋地宣布,Appium Conf 已回归并在2024年上线!这是官方的Appium会议,您可以在会上了解Appium 2中的所有新内容。 并探索设备自动化测试、移动应用程序和桌面应用程序的最新版本。
<!-- more -->
@@ -19,7 +19,7 @@ date: 2024-07-13
## 标记您的日历:2024年9月13日
2024年9月13日,请加入我们,参加Appium团队和世界顶级移动测试和自动化专家为期一天的鼓舞人心且实用的演讲。 随着Appium 2.0的成熟,这是您深入了解并学习如何最大限度地发挥其潜力的机会。
2024年9月13日,请加入我们,参加Appium团队和世界顶级移动测试和自动化专家为期一天的鼓舞人心且实用的演讲。随着Appium 2.0的成熟,这是您深入了解并学习如何最大限度地发挥其潜力的机会。
## 为什么参加了2024年的Appium Conf ?
@@ -30,16 +30,16 @@ date: 2024-07-13
## 征集提案:分享您的知识!
我们正在寻找来自全球各地的各种演讲者,他们渴望分享移动测试自动化的创新想法和用例。 如果您有使用Appium的经验和见解,可以使测试从业者社区受益,我们想听听您的意见!
我们正在寻找来自全球各地的各种演讲者,他们渴望分享移动测试自动化的创新想法和用例。如果您有使用Appium的经验和见解,可以使测试从业者社区受益,我们想听听您的意见!
不要错过这个机会,为Appium社区做出贡献,并在全球舞台上展示你的专业知识。
## 如何提交您的提案
访问我们的网站 [https://appiumconf.com/](https://appiumconf.com/) 并[提交提案](https://confengine.com/conferences/appium-conf-2024/proposals?utm_medium=referal&utm_source=appium.io&utm_campaign=launch)。 我们很高兴看到您在 Appium 中的创新想法和经验!
访问我们的网站 [https://appiumconf.com/](https://appiumconf.com/) 并[提交提案](https://confengine.com/conferences/appium-conf-2024/proposals?utm_medium=referal&utm_source=appium.io&utm_campaign=launch)。我们很高兴看到您在 Appium 中的创新想法和经验!
## 敬请期待
关注我们的网站和社交媒体频道以了解会议日程安排的更新, 主旨演讲人和2024年Appium Conf 的更令人兴奋的细节。
加入我们来塑造移动设备测试自动化的未来。 立即提交您的提案,成为这一激动人心的活动的一部分!
加入我们来塑造移动设备测试自动化的未来。立即提交您的提案,成为这一激动人心的活动的一部分!
@@ -45,7 +45,7 @@ npx appium-docs init
请参考 [MkDocs 文档](https://www.mkdocs.org/user-guide/writing-your-docs/) 来了解如何组织与构建你的文档结构。
### Helping LLMs Use Your Documentation
### 帮助LLMs 使用您的文档
The [`/llms.txt` convention](https://llmstxt.org/) is a Markdown file at the root of a
documentation site, listing its most useful pages so that large language models can navigate the
@@ -2,7 +2,7 @@
title: 构建 Doctor Checks
---
Appium Doctor 的目标,是帮助用户完成驱动或插件的前置条件配置。 有时这些前置条件可能相当复杂,需要一定的技术知识。 Doctor checks是由扩展作者编写的普通 Node.js 类实例,它可以自动完成诊断并尝试修复发现的问题。 为了提供更好的使用体验,这些检查项有时也会是交互式的。
Appium Doctor 的目标,是帮助用户完成驱动或插件的前置条件配置。有时这些前置条件可能相当复杂,需要一定的技术知识。 Doctor checks是由扩展作者编写的普通 Node.js 类实例,它可以自动完成诊断并尝试修复发现的问题。为了提供更好的使用体验,这些检查项有时也会是交互式的。
本教程适用于希望帮助用户处理复杂配置或设置步骤的插件或驱动作者。
@@ -10,14 +10,13 @@ Appium Doctor 的目标,是帮助用户完成驱动或插件的前置条件配
### 类型要求
术语 `Doctor Check` 的字面含义,是指一个实现了 [IDoctorCheck 接口](https://github.com/appium/appium/blob/master/packages/types/lib/doctor.ts) 的 JavaScript 类实例。
这个接口定义了以下方法和属性:
术语 `Doctor Check` 的字面含义,是指一个实现了 [IDoctorCheck 接口](https://github.com/appium/appium/blob/master/packages/types/lib/doctor.ts) 的 JavaScript 类实例。这个接口定义了以下方法和属性:
- `diagnose(): Promise<DoctorCheckResult>`:包含诊断某个问题的代码
- `fix(): Promise<string|null>`:如果 `hasAutofix()` 返回 true,则修复实际问题;否则返回一段手动修复的说明。 如果此方法抛出名为 `FixSkippedError` 的异常,并且 `hasAutofix()` 返回 true,那么该调用结果将被忽略。
- `fix(): Promise<string|null>`:如果 `hasAutofix()` 返回 true,则修复实际问题;否则返回一段手动修复的说明。如果此方法抛出名为 `FixSkippedError` 的异常,并且 `hasAutofix()` 返回 true,那么该调用结果将被忽略。
- `hasAutofix(): boolean`:表示调用 `fix()` 是否能解决发现的问题
- `isOptional(): boolean`:表示发现的问题是否可以被忽略且不是阻塞性问题
- `log: AppiumLogger`:可用于日志输出。 这个属性可以由实例自行赋值,也可以由 Appium 服务在未赋值时自动提供。
- `log: AppiumLogger`:可用于日志输出。这个属性可以由实例自行赋值,也可以由 Appium 服务在未赋值时自动提供。
`diagnose()` 方法返回的 `DoctorCheckResult` 对象必须包含以下属性:
@@ -27,7 +26,7 @@ Appium Doctor 的目标,是帮助用户完成驱动或插件的前置条件配
### 清单要求
一个扩展可以向 Appium 导出多个 Doctor checks。 为了让这些检查项在对应扩展安装后能够被服务端 CLI 正确识别,它们需要在package .json 清单文件中列在 `appium.doctor.checks` 部分,格式类似下面这样:
一个扩展可以向 Appium 导出多个 Doctor checks。为了让这些检查项在对应扩展安装后能够被服务端 CLI 正确识别,它们需要在package .json 清单文件中列在 `appium.doctor.checks` 部分,格式类似下面这样:
```json
// ...
@@ -1,22 +1,17 @@
---
title: Appium's Config System
title: Appium 的配置系统
---
Appium includes support for [configuration files](../guides/config.md). A configuration file is
intended to have (nearly) 1:1 parity with command-line arguments. An end user can supply Appium with
a configuration file, CLI args, or both (the args have precedence over the config file).
Appium 支持[配置文件](../guides/config.md)。配置文件的目标是与命令行参数做到(近乎)1:1 对应。最终用户可以为 Appium 提供配置文件、CLI 参数,或两者同时提供(参数优先级高于配置文件)。
This document will be a technical overview of how the configuration system works. It is intended
for Appium contributors, but will also explain the system's fundamental features.
本文是配置系统工作原理的技术性概览。它面向 Appium 贡献者,但也会说明该系统的基本特性。
## Reading a Config File
## 读取配置文件
A config file is a JSON, JavaScript, or YAML file which can be validated against a schema. By
default, this file will be named `.appiumrc.{json,js,yaml,yml}` and should be in the root of the
project which depends upon `appium`. Other filenames and locations are supported via the `--config <file>` flag. For obvious reasons, the `config` argument is disallowed within config files.
配置文件是一个可以按 schema 校验的 JSON、JavaScript 或 YAML 文件。默认情况下,该文件名为
`.appiumrc.{json,js,yaml,yml}`,应放在依赖 `appium` 的那个项目的根目录中。其他文件名和位置也支持,需通过 `--config <file>` 标志指定。显而易见,配置文件内部不允许出现 `config` 参数。
In lieu of a separate file, configuration can be embedded in a project's `package.json` using the
`appiumConfig` property, e.g.,:
除了独立文件之外,还可以把配置内嵌在项目的 `package.json` 中,使用 `appiumConfig` 属性,例如:
```json
{
@@ -28,248 +23,172 @@ In lieu of a separate file, configuration can be embedded in a project's `packag
}
```
When an Appium server is started via the `appium` executable, the `init` function in `lib/main.js`
will call into `lib/bootstrap/config-file.js` to load and/or search for a configuration file and in
`package.json`.
当通过 `appium` 可执行文件启动 Appium 服务器时,`lib/main.js` 中的 `init` 函数会调用`lib/bootstrap/config-file.js`,去加载和/或查找配置文件以及 `package.json` 中的配置。
!!! note
!!! 注意
```
It is not an error if configuration isn't found!
没有找到配置信息并不是错误!
```
The [`lilconfig`](https://npm.im/lilconfig) package provides the search & load functionality; refer
to its documentation for more information about the search paths. Additionally, Appium provides
support for config files written in YAML via the package [`yaml`](https://npm.im/yaml).
[`lilconfig`](https://npm.im/lilconfig) 包提供了查找与加载功能;关于搜索路径的更多信息请参考它的文档。此外,Appium 通过 [`yaml`](https://npm.im/yaml) 包支持用 YAML 编写的配置文件。
If a config file is found and successfully [validated](#validation), the result will be merged with
a set of defaults and any additional CLI arguments. CLI arguments have precedence over config
files, and config files have precedence over defaults.
如果找到配置文件并成功[通过校验](#校验),其结果会与一组默认值以及额外的 CLI 参数合并。 CLI 参数的优先级高于配置文件,配置文件的优先级高于默认值。
## Validation
## 校验
The same system is used for _both_ validation of config files _and_ command-line arguments.
用于校验配置文件和命令行参数的是同一套系统。
The package [`ajv`](https://npm.im/ajv) provides validation. Of course, to make `ajv` validate
anything, it must be provided a _schema_.
校验由 [`ajv`](https://npm.im/ajv) 包提供。当然,要让 `ajv` 校验任何东西,必须先给它一个 _schema_。
The _base_ schema is a JSON Schema
Draft-7-compliant object exported by
`lib/schema/appium-config-schema.js`. This schema defines configuration _native to Appium_, and
only concerns its behavior as a _server_; it does not define configuration for any other
functionality (e.g., the `plugin` or `driver` subcommands).
_基础_ schema 是由 `lib/schema/appium-config-schema.js` 导出的一个符合 [JSON Schema Draft-7](https://json-schema.org/draft/2020-12/json-schema-core.html) 的对象。该 schema 定义的是 _Appium 原生&#x7684;_&#x914D;置,并且只涉及它作&#x4E3A;_&#x670D;务&#x5668;_&#x7684;行为;它不定义其他任何功能(例如 `plugin` 或 `driver` 子命令)的配置。
!!! warning
!!! 警告
```
Note that this file is the _base_ schema; this will become painfully relevant.
请注意这个文件只是基础 schema;这一点后面会体现得很"刻骨铭心"。
```
This file is is _not_ a JSON file, because a) JSON is painful to work with for humans, b) is
especially reviled by @jlipps, and c) `ajv` accepts objects, not JSON files.
这个文&#x4EF6;_&#x4E0D;是_ JSON 文件,因为 a) JSON 对人来说很难编写,b) 它尤其让 @jlipps 讨厌,c) `ajv`接受的是对象而不是 JSON 文件。
It is more straightforward to explain how config files are validated, so we'll start there.
解释配置文件的校验流程更直接一些,所以我们从那里开始。
### Validating Config Files
### 校验配置文件
When a config file is found (`lib/bootstrap/config-file.js`), it will call the `validate` function exported
from `lib/schema/schema.js` with the contents of the config file. In turn, this asks `ajv` to
validate the data against the schema that Appium has provided it.
当找到配置文件后(`lib/bootstrap/config-file.js`),它会把配置文件的内容传给 `lib/schema/schema.js` 导出的 `validate` 函数。这一步请求 `ajv` 按 Appium 提供的 schema 来校验数据。
If the config file is invalid, errors will be generated to be displayed to the user. Finally, the
`init` function will detect these errors, display them, and the process will exit.
如果配置文件非法,就会生成用于展示给用户的错误。最终 `init` 函数会检测到这些错误并把它们显示出来,随后进程退出。
I hope that made sense, because this is the easy part.
我希望这段讲清楚了,因为这只是简单的部分。
### Validating CLI Arguments
### 校验命令行参数
As mentioned earlier, the same system is used for validating both config files and CLI arguments.
如前所述,校验配置文件和校验 CLI 参数用的是同一套系统。
Totally not judging, but Appium uses [`argparse`](https://npm.im/argparse) for its CLI argument
parsing. This package, and others like it, provides an API to define the arguments a command-line
Node.js script accepts, and will ultimately return an object representation of the user-supplied
arguments.
无意评判谁,Appium 用 [`argparse`](https://npm.im/argparse) 解析命令行参数。这类包提供了一套API 来定义一个命令行 Node.js 脚本可接受的参数,并最终返回用户所给参数的对象表示。
Just as the schema defines what's allowed in a config file, it also defines what's allowed on the
command-line.
正如 schema 定义了配置文件中允许出现什么,它也定义了命令行上允许出现什么。
#### Defining CLI Arguments via Schema
#### 通过 schema 定义 CLI 参数
CLI arguments must be _defined_ before their values can be validated.
必须&#x5148;_&#x5B9A;义_ CLI 参数,才能校验它们的值。
A JSON schema isn't a natural fit for defining CLI args--it needs some grease to make it work--but
it's close enough that we can do so with an adapter and some custom metadata.
JSON schema 并不是天然适合定义 CLI 参数的格式——得加点"润滑油"才能跑得起来——不过它足够接近,我们借助一个适配层和一些自定义元数据就能做到。
In `lib/cli/parser.js`, there's a wrapper around `argparse`'s `ArgumentParser`; it's called (wait
for it)... `ArgParser`. The wrapper exists because we're doing some custom things with `argparse`,
but is has nothing to do with the schema directly.
在 `lib/cli/parser.js` 中有一个对 `argparse` 的 `ArgumentParser` 的包装类;它叫.. `ArgParser`。之所以有这个包装,是因为我们对 `argparse` 做了一些自定义处理,但它与 schema 本身
没有直接关系。
An `ArgParser` instance is created and its `parseArgs()` method is called with the raw CLI
arguments. The definition of the accepted arguments comes from `lib/cli/args.js` in part--here, all
of the arguments _not_ intended for use with the `server` subcommand are hard-coded (e.g., the
`driver` subcommand and _its_ subcommands). `args.js` also contains a function `getServerArgs()`,
which in turn calls into `toParserArgs` in `lib/schema/cli-args.js`. `lib/schema/cli-args.js` can
be considered the "adapter" layer between `argparse` and the schema.
系统会创建一个 `ArgParser` 实例,并用原始的 CLI 参数调用它的 `parseArgs()` 方法。可接受参数的定义有一部分来自 `lib/cli/args.js`——在那里,所&#x6709;_&#x4E0D;_&#x7528;于 `server` 子命令的参数都是硬编码的(例如 `driver` 子命令以&#x53CA;_&#x5B83;_&#x7684;子命令)。 `args.js` 还包含一个 `getServerArgs()` 函数,它会调用 `lib/schema/cli-args.js` 中的 `toParserArgs`。 `lib/schema/cli-args.js` 可以被视为 `argparse` 与 schema 之间的"适配层"。
`toParserArgs` uses the `flattenSchema` function exported by `lib/schema/schema.js`, which
"squashes" the schema into a key/value representation. Then, `toParserArgs` iterates over each
key/value pair and "converts" it into a suitable `ArgumentOption` object for final handoff to
`ArgParser`.
`toParserArgs` 使用 `lib/schema/schema.js` 导出的 `flattenSchema` 函数,该函数会把 schema
"压扁"成键/值表示。然后 `toParserArgs` 遍历每一对键/值,把它"转换"成合适的 `ArgumentOption` 对象,最终交给 `ArgParser`。
This adapter (`cli-args.js`) is where most of the mess is hidden; let's explore this rat's nest
a bit further.
这个适配层(`cli-args.js`)正是藏污纳垢之处;我们再来翻翻这个老鼠窝。
##### CLI & Schema Incongruities
##### CLI 与 schema 的不一致之处
The conversion algorithm (see function `subSchemaToArgDef` in `lib/schema/cli-args.js`) is mostly
just hacks and special cases neatly packed into a function. Things that don't cleanly map from
`argparse` to a JSON schema include, but are not limited to:
转换算法(见 `lib/schema/cli-args.js` 中的 `subSchemaToArgDef` 函数)大体上就是把各种 hack 和
特例整齐地塞进一个函数里。无法从 `argparse` 干净地映射到 JSON schema 的事情包括但不限于:
- A schema cannot natively express "store the value of `--foo=<value>` in a property called `bar`" in a schema (this corresponds to the `ArgumentOption['dest']` prop).
- A schema cannot natively express aliases; e.g., `--verbose` can also be `-v`
- A schema `enum` is not restricted to multiple types, but `argparse`'s equivalent `ArgumentOption['choices']` prop _is_
- A schema does not know about `argparse`'s concept of "actions" (note that Appium is not currently using custom actions--though it did, and it could again).
- `argparse` has no native type for `email`, `hostname`, `ipv4`, `uri` etc., and the schema does
- Schema validation only _validates_, it does not perform translation, transformation, or coercion (mostly). `argparse` allows this.
- Schemas allow the `null` type, for whatever reason. Ever pass `null` on the CLI?
- `argparse` does not understand anything other than primitives; no objects, arrays, etc., and certainly not arrays of a particular type.
- Schema 无法原生表达"把 `--foo=<value>` 的值存到一个名为 `bar` 的属性里"(对应 `ArgumentOption['dest']` 属性)。
- Schema 无法原生表达别名;例如 `--verbose` 也可以写成 `-v`
- Schema 的 `enum` 不限于同一种类型,而 `argparse` 中对应的 `ArgumentOption['choices']` 属&#x6027;_&#x5374;_&#x6709;这个限制
- Schema 不知道 `argparse` 的"actions"概念(注意 Appium 目前并未使用自定义 action——尽管它曾经用过,将来也可能再用)。
- `argparse` 没有 `email`、`hostname`、`ipv4`、`uri` 等原生类型,而 schema 有
- Schema 校验只&#x505A;_&#x6821;验_,不做翻译、转换或类型强制转换。而 `argparse` 允许这些。
- Schema 允许 `null` 类型,天知道为什么。你在命令行上传过 `null` 吗?
- `argparse` 只理解原始类型;不理解对象、数组等,更不理解"某种特定类型的数组"。
All of the above cases and others are handled by the adapter.
以上所有这些情况以及其他情况都由适配层处理。
!!! warning
!!! 警告
```
Some decisions made in the adapter were arrived at via coin toss. If you are curious about why
something is the way it is, it's likely that it had to do _something_.
适配层中做出的某些决定,说白了是抛硬币定下来的。如果你好奇某处为什么是这个样子,那多半是因为它总得做点什么。
```
Let's look more closely at handling types.
我们再仔细看看类型是怎么处理的。
#### Argument Types via `ajv`
#### 借助 `ajv` 实现参数类型
While `argparse` allows consumers, via its API, to define the _type_ of various arguments (e.g.,
a string, number, boolean flag, etc.), Appium mostly avoids these built-in types. _Why is that?_
Well:
虽然 `argparse` 允许使用方通过 API 定义各种参数&#x7684;_&#x7C7B;型_(例如字符串、数字、布尔标志等),但 Appium 基本避开了这些内置类型。\*为什么呢?\*原因是:
1. We already know the type of an argument, because we've defined it in a schema.
2. `ajv` provides validation against a schema.
3. A schema allows for greater expression of types, allowed values, etc., than `argparse` can provide natively.
4. The expressiveness of a schema allows for better error messaging.
1. 我们已经知道参数的类型了,因为我们在 schema 里定义了它。
2. `ajv` 提供基于 schema 的校验。
3. 相比 `argparse` 原生能力,schema 能对类型、允许取值等做出更强的表达。
4. schema 的表达力更强,错误提示也就更准确。
To that end, the adapter eschews `argparse`'s built-in types (see allowed string values of
`ArgumentOption['type']`) and instead abuses the ability to provide a _function_ as a `type`. The
exception is _boolean_ flags, which do not have a `type`, but rather `action: 'store_true'`. The
world may never know why.
为此,适配层舍弃了 `argparse` 的内置类型(参见 `ArgumentOption['type']` 允许的字符串取值),转而利用"可以把一&#x4E2A;_&#x51FD;&#x6570;_&#x4F5C;为 `type`"这一能力。唯一的例外&#x662F;_&#x5E03;&#x5C14;_&#x6807;志,它们没有 `type`,而是用`action: 'store_true'`。其中的原因,恐怕永远无人知晓。
##### Types as Functions
##### 把函数当类型
When a `type` is a function, the function performs both validation _and_ coercion (if necessary).
So what are these functions?
当 `type` 是一个函数时,该函数同时负责校&#x9A8C;_&#x548C;_(必要时的)类型强制转换。那么这些函数到底是什么?
> Note: `type` is _omitted_ (and thus _not_ a function) from the `ArgumentOption` if the property
> type is `boolean`, and is instead provided an `action` property of `store_true`. Yes, this is
> weird. No, I don't know why.
> 注意:如果属性的类型是 `boolean`,`ArgumentOption` 会省略 `type`(因此它不是函数),转而给出一个值为 `store_true` 的 `action` 属性。是的,这很奇怪。我也不知道为什么。
Well... it depends upon the schema. But generally speaking, we create a _pipeline_ of functions,
each corresponding to a keyword in the schema. Let's take the example of the `port` argument. In
lieu of asking the OS which ports the `appium`-running user can bind to, this argument is expected
to be an integer between 1 and 65535. This turns out to be two functions which we combine into
a pipeline:
嗯……这取决于 schema。但一般而言,我们会创建一条函&#x6570;_&#x6D41;水线_,其中每个函数对应 schema 中的一个关键字。以 `port` 参数为例。我们没有去问操作系统 `appium` 运行能绑定哪些端口,而是期望该参数是 1 到 65535 之间的整数。这最终归结为两个函数,我们把它们组合成一条流水线:
1. Convert the value to an integer, if possible. Because _every value in `process.argv` is a string_, we must coerce if we want a number.
2. Use `ajv` to validate the integer against the schema for `port`. A schema lets us define a range via the `minimum` and `maximum` keywords. Read more about how this works in
1. 尽可能把值转换为整数。因为 _`process.argv` 中的每个值都是字符串_,所以我们想要数字就必须做强制转换。
2. 用 `ajv` 按 `port` 的 schema 校验这个整数。 Schema 允许我们通过 `minimum` 和 `maximum` 关键字定义取值范围。关于这部分如何工作的更多说明,请阅读下文。
Much like the config file validation, if errors are detected, Appium nicely tells the end-user and
the process exits w/ some help text.
与配置文件校验一样,如果检测到错误,Appium 会礼貌地告知用户,然后进程带着帮助文本退出。
For other arguments which are naturally of non-primitive types, things are not so straightforward.
对于那些天然不是原始类型的其他参数,事情就没这么简单了。
##### Transformers
##### 转换器
Remember how `argparse` doesn't understand arrays? What if the most ergonomic way to express
a value is, in fact, an array?
还记得 `argparse` 不理解数组这件事吗?如果表达某个值最顺手的方式恰好就是一个数组呢?
Well, Appium can't accept an array on the CLI, even though it can accept one in the config file.
But Appium _can_ accept a comma-delimited string (a CSV "line"). Or a string filepath referring to
a file which _contains_ a delimited list. Either way: by the time the value gets out of the
argument parser, it should be an array.
Appium 无法在命令行上接受数组,尽管它在配置文件中可以接受数组。但 Appium _可&#x4EE5;_&#x63A5;受逗号分隔的字符串(一行 CSV),或者接受一个文件路径字符串,指&#x5411;_&#x5305;&#x542B;_&#x5206;隔列表的文件。无论哪种方式:当值离开参数解析器时,它应当已经是一个数组。
And as mentioned above, the native facilities of a JSON schema cannot express this. However, it's
possible to define a _custom keyword_ which Appium can then detect and handle accordingly. So
that's what Appium does.
如前所述,JSON schema 的原生能力无法表达这一点。但可以定&#x4E49;_&#x81EA;定义关键字_,让 Appium 检测到并按相应方式处理。 Appium 正是这样做的。
In this case, a custom keyword `appiumCliTransformer` is registered with `ajv`. The value of
`appiumCliTransformer` (at the time of this writing) can be `csv` or `json`. In the base schema
file, `appium-config-schema.js`, Appium uses `appiumCliTransformer: 'csv'` if this behavior is
desired.
具体来说,系统向 `ajv` 注册了一个自定义关键字 `appiumCliTransformer`。 `appiumCliTransformer` 的取值(截至本文撰写时)可以是 `csv` 或 `json`。在基础 schema 文件 `appium-config-schema.js` 中,如果想要这种行为,Appium 会写 `appiumCliTransformer: 'csv'`。
!!! note
!!! 注意
```
Any property defined in the schema having type `array` will _automatically_ uses the `csv`
transformer. Likewise, a property having type `object` will use the `json` transformer. It's
conceivable that `array` may want to use the `json` transformer, but otherwise, the presence of
the `appiumCliTransformer` keyword on an `array`-or-`object`-typed property is not strictly
necessary.
schema 中任何类型为 `array` 的属性都会自动使用 `csv` 转换器。同理,类型为 `object` 的属性会使用 `json` 转换器。可以想象,`array` 也许会用得上 `json` 转换器,但除此之外,在 `array` 或 `object` 类型的属性上显式写 `appiumCliTransformer` 关键字并不是必需的。
```
The adapter (remember the adapter?) creates a pipeline function including a special "CSV
transformer" (transformers are defined in `lib/schema/cli-transformers.js`), and uses this function
as the `type` property of the `ArgumentOption` passed into `argparse`. In this case, the `type:
'array'` in the schema is ignored.
适配层(还记得适配层吗?)会创建一条包含特殊"CSV 转换器"的流水线函数(转换器定义在 `lib/schema/cli-transformers.js`),并把这个函数作为传给 `argparse` 的 `ArgumentOption` 的 `type` 属性。这种情况下,schema 中的 `type: 'array'` 会被忽略。
!!! note
!!! 注意
```
The config file doesn't _need_ to perform any complex transformation of values, because it
naturally allows Appium to define exactly what it expects. So Appium does no post-processing of
config file values.
配置文件并不需要对值做任何复杂转换,因为它天然允许 Appium 精确声明自己想要什么。所以 Appium 不会对配置文件中的值做任何后处理。
```
Properties that do not need this special treatment use `ajv` directly for validation. How this
works requires some explanation, so that's next.
不需要这种特殊处理的属性直接使用 `ajv` 来校验。这套机制需要一些解释,所以下面就讲。
#### Validation of Individual Arguments via `ajv`
#### 借助 `ajv` 校验单个参数
When we think of a JSON schema, we tend to think, "I have this JSON file and I want to validate it
against the schema". That's valid, and in fact Appium does just that with config files! However,
Appium does not do this when validating arguments.
说到 JSON schema,我们通常会想:"我有这么个 JSON 文件,要拿 schema 来校验它"。这没错,Appium 对配置文件干的正是这件事!但在校验参数时,Appium 并不这么做。
!!! note
!!! 注意
```
During implementation, I was tempted to mash all of the arguments together into
a config-file-like data structure and then validate it all at once. I think that would have
been _possible_, but since an object full of CLI arguments is a flat key/value structure and
the schema is not, this seemed like trouble.
在实现过程中,我曾想把所有参数塞进一个类似配置文件的数据结构里,然后一次性校验。我觉得那应该可行,但装满 CLI 参数的对象是一个扁平的键/值结构,而 schema 不是,所以这看起来自找麻烦。
```
Instead, Appium validates a value against a specific property _within_ the schema. To do this, it
maintains a mapping between a CLI argument definition and its corresponding property. The mapping
itself is a `Map` with a unique identifier for the argument as the key, and an `ArgSpec`
(`lib/schema/arg-spec.js`) object as the value.
相反,Appium 是针对 schema _内&#x90E8;_&#x7684;某个具体属性来校验一个值。为此它维护了一份 CLI 参数定义与其对应属性之间的映射。这个映射本身是一个 `Map`,键是该参数的唯一标识,值是 `ArgSpec`(`lib/schema/arg-spec.js`)对象。
An `ArgSpec` object stores the following metadata:
`ArgSpec` 对象保存以下元数据:
| Property Name | Description |
| --------------- | -------------------------------------------------------------------------------------------------------- |
| `name` | Canonical name of the argument, corresponding to the property name in the schema. |
| `extType?` | `driver` or `plugin`, if appropriate |
| `extName?` | Extension name, if appropriate |
| `ref` | Computed `$id` of the property in the schema |
| `arg` | Argument as accepted on CLI, without leading dashes |
| `dest` | Property name in parsed arguments object (as returned by `argparse`'s `parse_args()`) |
| `defaultValue?` | Value of the `default` keyword in schema, if appropriate |
| 属性名 | 说明 |
| --------------- | ---------------------------------------------------- |
| `name` | 参数的规范名称,对应 schema 中的属性名。 |
| `extType?` | 若适用,值为 `driver` 或 `plugin` |
| `extName?` | 若适用,值为扩展名称 |
| `ref` | 该属性在 schema 中计算得到的 `$id` |
| `arg` | 在 CLI 上接受形式,不含前导短横线 |
| `dest` | 解析后的参数对象中的属性名(即 `argparse` 的 `parse_args()` 返回结果中的键) |
| `defaultValue?` | 若适用,值为 schema 中 `default` 关键字定义的值 |
When a schema is [finalized](#schema-loading), the `Map` is populated with `ArgSpec` objects
for all known arguments.
当 schema 被[定稿](#schema-的加载)时,这个 `Map` 会被填上所有已知参数的 `ArgSpec` 对象。
So when the adapter is creating the pipeline of functions for the argument's `type`, it already has
an `ArgSpec` for the argument. It creates a function which calls `validate(value, ref)` (in
`lib/schema/schema.js`) where `value` is whatever the user provided, and `ref` is the `ref`
property of the `ArgSpec`. The concept is that `ajv` can validate using _any_ `ref` it knows about;
each property in a schema can be referenced by this `ref` whether it's defined or not. To help
visualize, if a schema is:
因此,当适配层为参数的 `type` 构建函数流水线时,它手上已经有该参数的 `ArgSpec` 了。它会创建一个调用 `validate(value, ref)`(位于 `lib/schema/schema.js`)的函数,其中 `value` 是用户提供的任意
值,`ref` 是该 `ArgSpec` 的 `ref` 属性。其理念是:`ajv` 可以用它认识&#x7684;_&#x4EFB;意_ `ref` 来校验;schema中的每个属性都能被这个 `ref` 引用,无论它是否已定义。为了帮助理解,如果 schema 是:
```json
{
@@ -283,78 +202,54 @@ visualize, if a schema is:
}
```
The `ref` of `foo` would be `my-schema.json#/properties/foo`. Assuming our `Ajv` instance knows
about this `my-schema.json`, then we can call its `getSchema(ref)` method (which has a `schema`
property, but is a misnomer nonetheless) to get a validation function; `validate(value, ref)` in
`schema.js` calls this validation function.
那么 `foo` 的 `ref` 就是 `my-schema.json#/properties/foo`。假设我们的 `Ajv` 实例认识这个 `my-schema.json`,就可以调用它的 `getSchema(ref)` 方法(该方法返回的对象有个 `schema` 属性,不过方法名本身多少有点名不副实)得到一个校验函数;`schema.js` 中的 `validate(value, ref)` 调用的就是
这个校验函数。
!!! note
!!! 注意
```
The schema spec says a schema author can supply an explicit `$id` keyword to override this;
it's unsupported by Appium at this time. If needed, extension authors must carefully use `$ref`
without custom `$id`s. It's highly unlikely an extension would have a schema so complicated as
to need this, however; Appium itself doesn't even use `$ref` to define its own properties!
schema 规范说,schema 作者可以提供显式的 `$id` 关键字来覆盖这一点;Appium 目前不支持这样做。如有需要,扩展作者必须在不使用自定义 `$id` 的前提下谨慎使用 `$ref`。不过扩展的 schema 复杂到需要这种程度几乎不可能;连 Appium 自己定义属性时都没用到 `$ref`!
```
Next, let's take a look at how Appium loads schemas. This actually happens _before_ any argument
validation.
接下来看看 Appium 是如何加载 schema 的。这一步实际上发生在任何参数校&#x9A8C;_&#x4E4B;前_。
## Schema Loading
## Schema 的加载
Let's ignore extensions for a moment, and start with the base schema.
我们先暂时抛开扩展,从基础 schema 说起。
When something first imports the `lib/schema/schema.js` module, an instance of an `AppiumSchema` is
created. This is a singleton, and its methods are exported from the module (all of which are bound
to the instance).
当某个地方首次 import `lib/schema/schema.js` 模块时,会创建一个 `AppiumSchema` 实例。它是一个单例,模块导出的是它的各个方法(全部绑定到该实例上)。
The constructor does very little; it instantiates an `Ajv` instance and configures it with Appium's
[custom keywords](#custom-keyword-reference) and adds support for the `format` keyword via the
[ajv-formats](https://npm.im/ajv-formats) module.
构造函数做很少的事:它实例化一个 `Ajv`,用 Appium 的[自定义关键字](#自定义关键字参考)进行配置,并通过 [ajv-formats](https://npm.im/ajv-formats) 模块加入对 `format` 关键字的支持。
Otherwise, the `AppiumSchema` instance does not interact with the `Ajv` instance until its
`finalize()` method (exported as `finalizeSchema()`) is called. When this method is called, we're
saying "we are not going to add any more schemas; go ahead and create `ArgSpec` objects and
register schemas with `ajv`".
除此之外,在 `AppiumSchema` 实例的 `finalize()` 方法(以 `finalizeSchema()` 之名导出)被调用之前,
它不会与 `Ajv` 实例发生任何交互。当这个方法被调用时,等于在说"我们不会再添加更多 schema 了;请开始创建 `ArgSpec` 对象并向 `ajv` 注册 schema 吧"。
When does finalization happen? Well:
那定稿什么时候发生呢?是这样的:
1. When the `appium` executable begins, it _checks for and configures extensions_ (hand-wave) in `APPIUM_HOME`.
2. Only then does it start to think about arguments--it instantiates an `ArgParser`, which (as you'll recall) runs the adapter to convert the schema to arguments.
3. _Finalization happens here_--when creating the parser. Appium need the schema(s) to be registered with `ajv` in order to create validation functions for arguments.
4. Thereafter, Appium parses the arguments with the `ArgParser`.
5. Finally, decides what to do with the returned object.
1. `appium` 可执行文件启动时,&#x4F1A;_&#x68C0;查并配置 `APPIUM_HOME` 中的扩展_(这里先含糊带过)。
2. 然后它才开始考虑参数——实例化一个 `ArgParser`,而它(还记得吗)会运行适配层,把 schema 转换成参数。
3. _定稿就发生在这里_——即创建解析器的时候。 Appium 需要这些 schema 已注册到 `ajv`,才能为参数创建校验函数。
4. 随后,Appium 用 `ArgParser` 解析参数。
5. 最后,决定如何处理返回的对象。
Without extensions, `finalize()` still knows about the Appium base schema
(`appium-config-schema.js`), and just registers that. However, step 1. above is doing a _lot of
work_, so let's look at how extensions come into play.
即使没有扩展,`finalize()` 依然知道 Appium 的基础 schema(`appium-config-schema.js`),并注册它。但上面第 1 步可干了不&#x5C11;_&#x91CD;活_,所以我们来看看扩展是如何介入的。
## Extension Support
## 扩展支持
One of the design goals of this system is the following:
这套系统的一个设计目标是:
_An extension should be able to register custom CLI arguments with the Appium, and a user should be
able to use them like any other argument_.
_扩展应能够向 Appium 注册自定义 CLI 参数,而用户应能像使用任何其他参数一样使用它们。_
Previously, Appium accepted arguments in this manner (via `--driverArgs`), but validation was
hand-rolled and required extension implementors to use a custom API. It also required the user to
awkwardly pass a JSON string as the configuration on the command-line. Further, no contextual help
(via `--help`) existed for these arguments.
以前,Appium 是以另一种方式接受这些参数(通过 `--driverArgs`),但校验是手写的,还要求扩展实现方使用一套自定义 API。用户也不得不别扭地在命令行上传入一个 JSON 字符串作为配置。而且这些参数没有上下文帮助信息(通过 `--help` 看不到)。
Now, by providing a schema for its options, a driver or plugin can register CLI arguments and
config file schemas with Appium.
如今,只要为其选项提供 schema,驱动或插件就能向 Appium 注册 CLI 参数和配置文件 schema。
To register a schema, an extension must provide the `appium.schema` property in its `package.json`.
The value may be a schema or a path to a schema. If the latter, the schema should be JSON or
a CommonJS module (ESM not supported at this time, nor is YAML).
要注册一个 schema,扩展必须在其 `package.json` 中提供 `appium.schema` 属性。其值可以是 schema 本身,也可以是指向 schema 的路径。若是后者,schema 应为 JSON 或一个 CommonJS 模块(暂不支持 ESM,也不支持 YAML)。
For any property in this schema, the property will appear as a CLI argument of the form
`--<extension-type>-<extension-name>-<property-name>`. For example, if the `fake` driver provides
a property `foo`, the argument will be `--driver-fake-foo`, and will show in `appium server --help`
like any other CLI argument.
对于该 schema 中的每个属性,它都会以 `--<extension-type>-<extension-name>-<property-name>` 形式的 CLI 参数出现。例如,若 `fake` 驱动提供了一个 `foo` 属性,那么参数就是 `--driver-fake-foo`,并且会像其他任何 CLI 参数一样出现在
`appium server --help` 中。
The corresponding property in a config file would be
`server.<extension-type>.<extension-name>.<property-name>`, e.g.:
配置文件中对应的属性则是 `server.<extension-type>.<extension-name>.<property-name>`,例如:
```json
{
@@ -368,20 +263,15 @@ The corresponding property in a config file would be
}
```
The naming convention described above avoids problems of one extension type having a name conflict
with a different extension type.
上面描述的命名约定避免了某一类扩展的名称与另一类扩展发生冲突的问题。
!!! note
!!! 注意
```
While an extension can provide aliases via `appiumCliAliases`, "short" flags are disallowed,
since all arguments from extensions are prefixed with `--<extension-type>-<extension-name>-`.
The extension name and argument name will be kebab-cased for the CLI, according to [Lodash's
rules](https://lodash.com/docs/4.17.15#kebabCase) around kebab-casing.
虽然扩展可以通过 `appiumCliAliases` 提供别名,但不允许使用"短"标志,因为来自扩展的所有参数都以 `--<extension-type>-<extension-name>-` 为前缀。扩展名和参数名会按 [Lodash 关于kebab-case 的规则](https://lodash.com/docs/4.17.15#kebabCase)转换成 kebab-case 形式用于命令行。
```
The schema object will look much like Appium's base schema, but it will only have top-level
properties (nested properties are currently unsupported). Example:
schema 对象看起来会与 Appium 的基础 schema 很像,但它只有顶层属性(暂不支持嵌套属性)。示例:
```json
{
@@ -397,20 +287,13 @@ properties (nested properties are currently unsupported). Example:
}
```
As written in a user's config file, this would be the `server.driver.cowabunga.fizz` property.
写在用户的配置文件中时,它就是 `server.driver.cowabunga.fizz` 属性。
When extensions are loaded, the `schema` property is verified and the schema is registered with the
`AppiumSchema` (it is _not_ registered with `Ajv` until `finalize()` is called).
加载扩展时,系统会验证 `schema` 属性,并把该 schema 注册到 `AppiumSchema`(此&#x65F6;_&#x4E0D;&#x4F1A;_&#x6CE8;册到 `Ajv`,直到 `finalize()` 被调用才注册)。
During finalization, each registered schema is added to the `Ajv` instance. The schema is assigned
an `$id` based on the extension type and name (which overrides whatever the extension provides, if
anything). Schemas are also forced to disallowed unknown arguments via the `additionalProperties:
false` keyword.
在定稿阶段,每个已注册的 schema 都会被加入 `Ajv` 实例。 schema 会被赋予一个基于扩展类型和扩展名的 `$id`(这会覆盖扩展自己提供的 `$id`,如果有的话)。 schema 还会被强制通过 `additionalProperties: false` 关键字禁止未知参数。
Behind the scenes, the base schema has `driver` and `plugin` properties which are objects. When
finalized, a property is added to each--corresponding to an extension name--and the value of this
property is a reference to the `$id` of a property in the extension schema. For example, the
`server.driver` property will look like this:
在幕后,基础 schema 有两个类型为对象的属性:`driver` 和 `plugin`。定稿时会向各自添加一个属性——以扩展名命名——其值是指向该扩展 schema 中某属性 `$id` 的引用。例如 `server.driver` 属性看起来会是这样:
```json
{
@@ -422,47 +305,36 @@ property is a reference to the `$id` of a property in the extension schema. For
}
```
This is why we call it the "base" schema--it is _mutated_ when extensions provide schemas. The
extension schemas are kept separately, but the _references_ are added to the schema before it's
ultimately added to `ajv`. This works because an `Ajv` instance understands references _from_ any
schema it knows about _to_ any schema it knows about.
这就是为什么我们把它称为"基础" schema——当扩展提供 schema 时它会&#x88AB;_&#x6539;写_。扩展的 schema 单独保存,但在最终加入 `ajv` 之前,会先&#x628A;_&#x5F15;&#x7528;_&#x6DFB;加进这个 schema。这样做行得通,是因为一个 `Ajv` 实例能够理解从它认识的任意 schema _指&#x5411;_&#x5B83;认识的任意 schema 的引用。
!!! note
!!! 注意
```
This makes it impossible to provide a complete static schema for Appium _and_ the installed
extensions (as of Nov 5 2021). A static `.json` schema _is_ generated from the base (via a Gulp
task), but it does not contain any extension schemas. The static schema also has uses beyond
Appium; e.g., IDEs can provide contextual error-checking of config files this way. Let's solve
this?
这使得无法为 Appium _以及_已安装的扩展提供一份完整的静态 schema(截至 2021 年 11 月 5 日)。静态的 `.json` schema _会_由基础 schema 生成(通过一个 Gulp 任务),但它不包含任何扩展 schema。静态 schema 也有 Appium 之外的用途;例如 IDE 可以借此为配置文件提供上下文错误检查。这个问题要不要一起来解决呢?
```
Just like how we look up the reference ID of a particular argument in the base schema, validation
of arguments from extensions happens the exact same way. If the `cowabunga` driver has the schema
ID `driver-cowabunga.json`, then the `fizz` property can be referenced from any schema registered
with `ajv` via `driver-cowabunga.json#/properties/fizz`. "Base" schema arguments begin with
`appium.json#properties/` instead.
就像我们在基础 schema 中查找某个特定参数的引用 ID 一样,来自扩展的参数的校验也走完全相同的路径。如果 `cowabunga` 驱动的 schema ID 是 `driver-cowabunga.json`,那么 `fizz` 属性就可以通过
`driver-cowabunga.json#/properties/fizz` 被任何注册到 `ajv` 的 schema 引用。而"基础" schema 的参数则以 `appium.json#properties/` 开头。
## Development Environment Support
## 开发环境支持
During the flow of development, a couple extra tasks have been automated to maintain the base
schema:
在开发流程中,有几个额外任务被自动化了,以维护基础 schema:
- As a post-transpilation step, a `lib/appium-config.schema.json` gets generated from
- `lib/schema/appium-config-schema.js` (in addition to its CJS counterpart generated by Babel).
- This file is under version control. It ends up being _copied_ to
- `build/lib/appium-config.schema.json` in this step. A pre-commit hook (see
- `scripts/generate-schema-declarations.js` in the root monorepo) generates
- a `types/appium-config-schema.d.ts` from the above JSON file. The types in `types/types.d.ts`
- depend upon this file. This file is under version control.
- 作为转译之后的一个步骤,会由 `lib/schema/appium-config-schema.js` 生成 `lib/appium-config.schema.json`
- (除 Babel 生成的 CJS 对应物之外)。
- 该文件纳入版本控制。它会在这一步被 _复制_ 到本仓库的
- `build/lib/appium-config.schema.json` 。一个 pre-commit 钩子
- (见根 monorepo 中的 `scripts/generate-schema-declarations.js`)
- 会由上述 JSON 文件生成 `types/appium-config-schema.d.ts`。 `types/types.d.ts` 中的类型
- 依赖于该文件。该文件同样纳入版本控制。
## Custom Keyword Reference
## 自定义关键字参考
Keywords are defined in `lib/schema/keywords.js`.
关键字定义在 `lib/schema/keywords.js` 中。
- `appiumCliAliases`: allows a schema to express aliases (e.g., a CLI argument can be `--verbose` or `-v`). This is an array of strings. Strings shorter than three (3) characters will begin with a single dash (`-`) instead of a double-dash (`--`). Note that any argument provided by an extension will begin with a double-dash, because these are required to have the `--<extension-type>-<extension-name>-` prefix.
- `appiumCliDest`: allows a schema to specify a custom property name in the post-`argprase` arguments objects. If not set, this becomes a camelCased string.
- `appiumCliDescription`: allows a schema to override the description of the argument when displayed on the command-line. This is useful paired with `appiumCliTransformer` (or `array`/`object`-typed properties), since there's a substantial difference between what a CLI-using user can provide vs. what a config-file-using user can provide.
- `appiumCliTransformer`: currently a choice between `csv` and `json`. These are custom functions which post-process a value. They are not used when loading & validating config files, but the idea should be that they result in the same object you'd get if you used whatever the config file wanted (e.g., an array of strings). `csv` is for comma-delimited strings and CSV files; `json` is for raw JSON strings and `.json` files.
- `appiumCliIgnore`: If `true`, do not support this property on the CLI.
- `appiumDeprecated`: If `true`, the property is considered "deprecated", and will be displayed as such to the user (e.g., in the `--help` output). Note the JSON Schema draft-2019-09 introduces a new keyword `deprecated` which we should use instead if upgrading to this metaschema. When doing so, `appiumDeprecated` should itself be marked as `deprecated`.
- `appiumCliAliases`:允许 schema 表达别名(例如某个 CLI 参数可以写成 `--verbose` 或 `-v`)。它是一个字符串数组,长度小于三个字符的项会以单短横线(`-`)而非双短横线(`--`)开头。注意,扩展提供的任何参数都会以双短横线开头,因为它们必须带 `--<extension-type>-<extension-name>-` 前缀。
- `appiumCliDest`:允许 schema 指定 `argparse` 解析后参数对象中的自定义属性名。若不设置,则是一个 camelCase 字符串。
- `appiumCliDescription`:允许 schema 覆盖参数在命令行帮助中展示的描述。它与 `appiumCliTransformer`(或 `array`/`object` 类型的属性)配合使用很有价值,因为"用命令行的用户能提供什么"与"用配置文件的用户能提供什么"之间存在显著差别。
- `appiumCliTransformer`:目前可选 `csv` 或 `json`。它们是自定义函数,用于对值做后处理。加载和校验配置文件时不会用到它们;其设计目标是让处理结果与直接使用配置文件时得到的对象一致(例如字符串数组)。 `csv` 用于逗号分隔字符串和 CSV 文件;`json` 用于原始 JSON 字符串和 `.json` 文件。
- `appiumCliIgnore`:若为 `true`,则该属性不支持通过 CLI 提供。
- `appiumDeprecated`:若为 `true`,该属性被视为"已废弃",并会以这种方式向用户展示(例如在 `--help` 输出中)。注意 JSON Schema draft-2019-09 引入了新关键字 `deprecated`,如果升级到该 schema,我们应该改用它;届时 `appiumDeprecated` 也应标记为 `deprecated`。
+1 -1
View File
@@ -5,7 +5,7 @@ hide:
title: 开发 Appium 扩展
---
Appium 采用模块化结构,这意味着 Appium 扩展(驱动和插件)与主 Appium 模块是解耦的;你只需要安装自己想要使用的扩展即可。 这种模块化结构还使得开发全新的扩展变得成为可能。
Appium 采用模块化结构,这意味着 Appium 扩展(驱动和插件)与主 Appium 模块是解耦的;你只需要安装自己想要使用的扩展即可。这种模块化结构还使得开发全新的扩展变得成为可能。
Appium 文档的这一部分,旨在帮助有志于开发者创建自己的 Appium 扩展:
@@ -5,18 +5,16 @@ hide:
title: 屏蔽敏感日志数据
---
自 Appium server 2.18.0 版本起,已经具备了在日志中屏蔽敏感值的能力。 下面的教程将介绍如何在第三方扩展中使用这一功能。
自 Appium server 2.18.0 版本起,已经具备了在日志中屏蔽敏感值的能力。下面的教程将介绍如何在第三方扩展中使用这一功能。
## 为什么这很有用
将密码、令牌等敏感信息从服务器日志中隐藏起来是合理且必要的,这样即使日志落入错误的人手中,也不会意外泄露敏感数据。
Appium server 已经提供了一种通过 [过滤](../guides/log-filters.md) 来处理日志记录的方式,不过它本身存在一些限制。
当前这种方法则更为先进,但需要驱动/插件侧进行一些细调。
Appium server 已经提供了一种通过 [过滤](../guides/log-filters.md) 来处理日志记录的方式,不过它本身存在一些限制。当前这种方法则更为先进,但需要驱动/插件侧进行一些细调。
## 使用方法
假设你的扩展使用的是标准内置的 [@appium/logger](https://www.npmjs.com/package/@appium/logger)。
要让日志中的某个值被替换成通用的掩码,需要执行以下步骤:
假设你的扩展使用的是标准内置的 [@appium/logger](https://www.npmjs.com/package/@appium/logger)。要让日志中的某个值被替换成通用的掩码,需要执行以下步骤:
- 将日志表达式改为包裹敏感值并进行格式化,例如:
@@ -34,6 +32,4 @@ Appium server 已经提供了一种通过 [过滤](../guides/log-filters.md) 来
格式化过程会通过标准的 Node.js [util.format](https://nodejs.org/api/util.html#utilformatformat-args) API 完成。
- 在发送相应的服务器请求时,如果这个日志行被使用并且应该被掩码处理,请额外添加自定义请求头 `X-Appium-Is-Sensitive`,其值为 `1` 或 `true`(大小写不敏感)。
如果没有这个请求头,上述日志值将不会被掩码处理。
这样就可以根据扩展处理的具体请求来有条件地屏蔽日志记录;如果日志表达式位于公共部分,就可以在这里统一控制。
- 在发送相应的服务器请求时,如果这个日志行被使用并且应该被掩码处理,请额外添加自定义请求头 `X-Appium-Is-Sensitive`,其值为 `1` 或 `true`(大小写不敏感)。如果没有这个请求头,上述日志值将不会被掩码处理。这样就可以根据扩展处理的具体请求来有条件地屏蔽日志记录;如果日志表达式位于公共部分,就可以在这里统一控制。
+1 -1
View File
@@ -19,7 +19,7 @@ title: 欢迎使用
欢迎来到 Appium 文档! Appium是一个开源项目和相关软件生态系统,旨在促进多种应用平台的用户界面自动化,包括移动端(iOS、Android、Tizen)、浏览器端(Chrome、Firefox、Safari)、桌面端(macOS、Windows)、电视端(Roku、tvOS、Android TV、三星)等!
<div style="text-align: center; margin-top: 2rem; font-style: italic;">
Appium 非常感谢主要合作伙伴的支持! (了解更多关于我们的赞助计划和贡献者补偿计划信息请<a
Appium 非常感谢主要合作伙伴的支持!(了解更多关于我们的赞助计划和贡献者补偿计划信息请<a
href="https://github.com/appium/appium/blob/master/GOVERNANCE.md#sponsorship">点击此处</a>)
<div class="homepageSponsors">
<div class="homepageSponsor">
+27 -29
View File
@@ -2,7 +2,7 @@
title: Appium 如何工作?
---
正如主页所述,Appium 是一个开源项目和相关软件生态系统,旨在促进多种应用平台的 UI 自动化。 随着 Appium 2 的发布,Appium 具有以下主要目标:[^1]
正如主页所述,Appium 是一个开源项目和相关软件生态系统,旨在促进多种应用平台的 UI 自动化。随着 Appium 2 的发布,Appium 具有以下主要目标:[^1]
- 在跨平台标准 API 下提供特定于平台的自动化功能
- 允许从任何编程语言轻松访问此 API
@@ -15,7 +15,7 @@ title: Appium 如何工作?
- 尽可能依赖不修改应用就能实现自动化(最好不要要求用户构建额外的 SDK 或软件,从而在测试版本和生产版本之间引入差异)的自动化工具
- 尽可能依赖现有标准而不是创建新标准
所以,以您了解的任何应用平台为例,如 iOS 或 Android。 Appium 希望开发者测试人员能够为该平台编写 UI 自动化代码,根据单一的统一 API。 根据 Appium 的目标,我们有很多问题需要回答以使其全部工作:
所以,以您了解的任何应用平台为例,如 iOS 或 Android。 Appium 希望开发者测试人员能够为该平台编写 UI 自动化代码,根据单一的统一 API。根据 Appium 的目标,我们有很多问题需要回答以使其全部工作:
- “单一统一”的 API 应该是什么?
- 我们如何将此 API 映射到特定平台的自动化行为?
@@ -25,67 +25,65 @@ title: Appium 如何工作?
- 我们如何&#x4E3A;_&#x6240;&#x6709;_&#x5E73;台启用自动化?
探索 Appium 对这些问题的答案可能不是学习 Appium 是什么的最快方式,但它肯定是一个好方法! 所以让我们深入了解。
探索 Appium 对这些问题的答案可能不是学习 Appium 是什么的最快方式,但它肯定是一个好方法!所以让我们深入了解。
## Appium 的 API 选择
Appium 非常幸运地继承了一个在 UI 自动化领域长期领先的技术,即 [Selenium](https://selenium.dev)。 Selenium 项目的目标是支持 Web 浏览器的 UI 自动化,因此我们可以将其视为占据 Appium 目标子集的一部分。 在此过程中,Selenium(以及后来合并的另一个名为 WebDriver 的项目)开发了一个相对稳定的浏览器自动化 API。
Appium 非常幸运地继承了一个在 UI 自动化领域长期领先的技术,即 [Selenium](https://selenium.dev)。 Selenium 项目的目标是支持 Web 浏览器的 UI 自动化,因此我们可以将其视为占据 Appium 目标子集的一部分。在此过程中,Selenium(以及后来合并的另一个名为 WebDriver 的项目)开发了一个相对稳定的浏览器自动化 API。
多年来,Selenium 与各种 Web 浏览器供应商和 [W3C](https://w3.org) 标准小组合作,将其 API 转变为官方 Web 浏览器标准,称为 [WebDriver 规范](https://w3c.github.io/webdriver/)。
所有主要浏览器现在都实现了符合 WebDriver 规范的自动化功能,而无需 Selenium 团队维护任何执行实际自动化的软件;标准获胜!
Appium 的最初目标是为移动应用(iOS 和 Android)开发自动化标准。 我们本可以发明一些新的东西,但为了团结力量并保持标准的标准化,我们决定采用 WebDriver 规范作为 Appium 的 API。[^2] 虽然网站和移动原生应用的用户交互并不完全相同(一旦我们开始考虑例如由简单遥控器控制的电视平台,差异会更大),但事实是大多数软件 UI 都非常相似。 这意味着 WebDriver 规范提供了自动化 API 原语(查找元素、与元素交互、加载页面或屏幕等) 这些原语或多或少映射到任何平台。
Appium 的最初目标是为移动应用(iOS 和 Android)开发自动化标准。我们本可以发明一些新的东西,但为了团结力量并保持标准的标准化,我们决定采用 WebDriver 规范作为 Appium 的 API。[^2] 虽然网站和移动原生应用的用户交互并不完全相同(一旦我们开始考虑例如由简单遥控器控制的电视平台,差异会更大),但事实是大多数软件 UI 都非常相似。这意味着 WebDriver 规范提供了自动化 API 原语(查找元素、与元素交互、加载页面或屏幕等)这些原语或多或少映射到任何平台。
[^2]: 从技术上讲,当 Appium 首次编写时,我们处理的是比 WebDriver 规范更古老的东西,称为 JSON Wire Protocol。 从那时起,Appium 继续与 W3C 规范一起演化,并完全符合 W3C 规范。
[^2]: 从技术上讲,当 Appium 首次编写时,我们处理的是比 WebDriver 规范更古老的东西,称为 JSON Wire Protocol。从那时起,Appium 继续与 W3C 规范一起演化,并完全符合 W3C 规范。
当然,Appium 希望支持用户交互从 Web 到移动或 Web 到电视确实不同的情况,因此 Appium 还利用 WebDriver 规范的内&#x7F6E;_&#x53EF;扩展性_。 结果是,无论您想要自动化哪个平台,当您使用 Appium 时,您将使用标准 WebDriver 规范,但有两个注意事项:
当然,Appium 希望支持用户交互从 Web 到移动或 Web 到电视确实不同的情况,因此 Appium 还利用 WebDriver 规范的内&#x7F6E;_&#x53EF;扩展性_。结果是,无论您想要自动化哪个平台,当您使用 Appium 时,您将使用标准 WebDriver 规范,但有两个注意事项:
- 我们可能无法在给定平台上支持特定的 WebDriver API 命令,因此某些命令可能不受支持(例如,在原生移动应用自动化世界中,获取或设置 cookie 是不可能的)。
- 我们可能支持超出 WebDriver API 命令列表的自动化行为,尽管任何此类命令将是有效的、符合规范的 WebDriver API 扩展。
您实际上如&#x4F55;_&#x4F7F;用_ WebDriver API,特别是 Appium 的背景下? 我们将在下面的[部分](#universal-programming-language-access)中介绍 Appium 如何提供通用编程语言访问。 您现在只需要知道 Appium 通过实现 WebDriver 协议来引入通用 UI 自动化接口。
您实际上如&#x4F55;_&#x4F7F;用_ WebDriver API,特别是 Appium 的背景下?我们将在下面的[部分](#universal-programming-language-access)中介绍 Appium 如何提供通用编程语言访问。您现在只需要知道 Appium 通过实现 WebDriver 协议来引入通用 UI 自动化接口。
## 平台自动化行为
下一个问题是,Appium 如何将此协议映射到广泛平台的自动化行为? 严格来说,Appium 没有! 它将此责任留给一种称为 Appium _驱动程序_ 的软件模块。 您可以阅读整个[驱动程序](./drivers.md)的介绍,所以我们现在不会深入细节。
下一个问题是,Appium 如何将此协议映射到广泛平台的自动化行为?严格来说,Appium 没有!它将此责任留给一种称为 Appium _驱动程序_ 的软件模块。您可以阅读整个[驱动程序](./drivers.md)的介绍,所以我们现在不会深入细节。
目前重要的是要理解驱动程序有点像 Appium 的可插拔模块,它赋予 Appium 自动化特定平台(或平台集,取决于驱动程序的目标)的能力。 最终,驱动程序的责任只是实现代表 WebDriver 协议的 Appium 内部接口。 它如何实现此接口完全取决于驱动程序,基于其在特定平台上实现自动化的策略。 通常,在细节上具有更多复杂性和难度,驱动程序通过依赖特定于平台的自动化技术来实现这一点。 例如,Apple 维护一种称为 [XCUITest](https://developer.apple.com/documentation/xctest/user_interface_tests) 的 iOS 自动化技术。 支持 iOS 应用自动化的 Appium 驱动程序称为 [XCUITest 驱动程序](https://github.com/appium/appium-xcuitest-driver),因为它最终所做的是将 WebDriver 协议转换为 XCUITest 库调用。
目前重要的是要理解驱动程序有点像 Appium 的可插拔模块,它赋予 Appium 自动化特定平台(或平台集,取决于驱动程序的目标)的能力。最终,驱动程序的责任只是实现代表 WebDriver 协议的 Appium 内部接口。它如何实现此接口完全取决于驱动程序,基于其在特定平台上实现自动化的策略。通常,在细节上具有更多复杂性和难度,驱动程序通过依赖特定于平台的自动化技术来实现这一点。例如,Apple 维护一种称为 [XCUITest](https://developer.apple.com/documentation/xctest/user_interface_tests) 的 iOS 自动化技术。支持 iOS 应用自动化的 Appium 驱动程序称为 [XCUITest 驱动程序](https://github.com/appium/appium-xcuitest-driver),因为它最终所做的是将 WebDriver 协议转换为 XCUITest 库调用。
驱动程序是独立、可插拔模块的原因之一是它们彼此完全不同。 构建和使用不同平台的驱动程序的工具和要求完全不同。 因此,Appium 让您只使用您自动化任务所需的驱动程序。 选择驱动程序并安装它们以便与您的 Appium 实例一起使用非常重要,以至于 Appium 有自己的 [CLI 来管理驱动程序](../reference/cli/extensions.md)。
驱动程序是独立、可插拔模块的原因之一是它们彼此完全不同。构建和使用不同平台的驱动程序的工具和要求完全不同。因此,Appium 让您只使用您自动化任务所需的驱动程序。选择驱动程序并安装它们以便与您的 Appium 实例一起使用非常重要,以至于 Appium 有自己的 [CLI 来管理驱动程序](../reference/cli/extensions.md)。
因此,为了回答我们最初的问题,Appium 为给定平台提供自动化功能访问的方式是 Appium 团队(或任何人[^3])为该平台编写一&#x4E2A;_&#x9A71;动程序_,实现 WebDriver 协议的尽可能多或少的部分。 然后,任何使用 Appium 的人都可以安装驱动程序。
因此,为了回答我们最初的问题,Appium 为给定平台提供自动化功能访问的方式是 Appium 团队(或任何人[^3])为该平台编写一&#x4E2A;_&#x9A71;动程序_,实现 WebDriver 协议的尽可能多或少的部分。然后,任何使用 Appium 的人都可以安装驱动程序。
[^3]: 您可以构建和分享自己的驱动程序! 查看[构建驱动程序](../developing/build-drivers.md)以了解更多关于如何在 Node.js 中开发可与 Appium 一起使用的驱动程序的信息。
[^3]: 您可以构建和分享自己的驱动程序!查看[构建驱动程序](../developing/build-drivers.md)以了解更多关于如何在 Node.js 中开发可与 Appium 一起使用的驱动程序的信息。
## 通用编程语言访问
但使用 Appium 意味着什么,或者看起来像什么? 由于 Appium 最终是一个 Node.js 程序,&#x5B83;_&#x53EF;&#x4EE5;_&#x770B;起来像将 Appium 及其驱动程序作为库导入到您自己的 Node.js 程序中。 但这不会满足 Appium 的目标,即为使用任何流行编程语言的人提供自动化功能。
但使用 Appium 意味着什么,或者看起来像什么?由于 Appium 最终是一个 Node.js 程序,&#x5B83;_&#x53EF;&#x4EE5;_&#x770B;起来像将 Appium 及其驱动程序作为库导入到您自己的 Node.js 程序中。但这不会满足 Appium 的目标,即为使用任何流行编程语言的人提供自动化功能。
幸运的是,Appium 继承 Selenium 的遗产意味着我们从一开始就有了这个问题的解决方案。 您看,WebDriver 规范实际上是一个基于 HTTP 的协议,这意味着它被设计为通过网络而不是单个程序的内存使用。
幸运的是,Appium 继承 Selenium 的遗产意味着我们从一开始就有了这个问题的解决方案。您看,WebDriver 规范实际上是一个基于 HTTP 的协议,这意味着它被设计为通过网络而不是单个程序的内存使用。
这种“客户端-服务器”架构的主要好处之一是,它允许自动化实现者(执行自动化的事物,在这种情况下是“服务器”)与自动化运行者(定义应该做什么自动化、以什么步骤等的事物,在这种情况下是“客户端”)完全不同。 基本上,所有“困难的东西”(弄清楚如何在给定平台上实现自动化)可以在服务器的一个地方处理,“瘦”客户端库可以在任何编程语言中编写,这些库只需以适合语言的方式编码对服务器的 HTTP 请求。 换句话说,假设存在高级 HTTP 库,只需在该语言中编写基本的 HTTP 客户端,就可以相对容易地将基本的 Appium / WebDriver 功能引入新的编程语言。
这种“客户端-服务器”架构的主要好处之一是,它允许自动化实现者(执行自动化的事物,在这种情况下是“服务器”)与自动化运行者(定义应该做什么自动化、以什么步骤等的事物,在这种情况下是“客户端”)完全不同。基本上,所有“困难的东西”(弄清楚如何在给定平台上实现自动化)可以在服务器的一个地方处理,“瘦”客户端库可以在任何编程语言中编写,这些库只需以适合语言的方式编码对服务器的 HTTP 请求。换句话说,假设存在高级 HTTP 库,只需在该语言中编写基本的 HTTP 客户端,就可以相对容易地将基本的 Appium / WebDriver 功能引入新的编程语言。
这里有几个重要的点供 Appium 用户了解:
- Appium 是一&#x4E2A;_&#x48;TTP 服务器_。 只要您想使用它进行自动化,它必须在某台计算机上作为进程运行。 它必须在网络上可访问,以方便您想要运行自动化的任何计算机访问(无论是同一台机器还是世界另一端的机器)。
- 除非您想编写原始 HTTP 调用或使用 cURL,否则请使用您选择的语言的 [Appium 客户端](clients.md)进行自动化。 每个客户端的目标是将 WebDriver 协议封装,以便您不必担心协议本身,而是可以使用感觉适合您语言的对象和方法。
- Appium 服务器和 Appium 客户&#x7AEF;_&#x4E0D;需&#x8981;_&#x5728;同一台计算机上运行。 您只需能够通过某个网络从客户端向服务器发送 HTTP 请求。
这大大促进了云提供商对 Appium 的使用,因为他们可以托管 Appium 服务器以及任何相关驱动程序和设备,您只需将客户端脚本指向他们的安全端点。
- Appium 是一&#x4E2A;_&#x48;TTP 服务器_。只要您想使用它进行自动化,它必须在某台计算机上作为进程运行。它必须在网络上可访问,以方便您想要运行自动化的任何计算机访问(无论是同一台机器还是世界另一端的机器)。
- 除非您想编写原始 HTTP 调用或使用 cURL,否则请使用您选择的语言的 [Appium 客户端](clients.md)进行自动化。每个客户端的目标是将 WebDriver 协议封装,以便您不必担心协议本身,而是可以使用感觉适合您语言的对象和方法。
- Appium 服务器和 Appium 客户&#x7AEF;_&#x4E0D;需&#x8981;_&#x5728;同一台计算机上运行。您只需能够通过某个网络从客户端向服务器发送 HTTP 请求。这大大促进了云提供商对 Appium 的使用,因为他们可以托管 Appium 服务器以及任何相关驱动程序和设备,您只需将客户端脚本指向他们的安全端点。
当然,这一切都不是关于“测试”本身,只是出于自动化目的使用 Appium 及其客户端库。 如果您想出于“测试”目的进行自动化,您可能希望利用测试运行器、测试框架等的帮助,这些都不需要与 Appium 相关;Appium“通用可访问性”的好处之一是它与您发现最适合您情况的任何工具集配合良好。
当然,这一切都不是关于“测试”本身,只是出于自动化目的使用 Appium 及其客户端库。如果您想出于“测试”目的进行自动化,您可能希望利用测试运行器、测试框架等的帮助,这些都不需要与 Appium 相关;Appium“通用可访问性”的好处之一是它与您发现最适合您情况的任何工具集配合良好。
## Appium 的巨大范围
Appium 的愿景(在单一 API 下自动化一切)是巨大的! 当然,比开源项目的核心维护者团队大得多。 那么 Appium 如何希望实现这一目标? 基本上,通过授权社区在 Appium 之上作&#x4E3A;_&#x5E73;&#x53F0;_&#x5F00;发功能。 这就是我们所谓的 Appium“生态系统”。
Appium 的愿景(在单一 API 下自动化一切)是巨大的!当然,比开源项目的核心维护者团队大得多。那么 Appium 如何希望实现这一目标?基本上,通过授权社区在 Appium 之上作&#x4E3A;_&#x5E73;&#x53F0;_&#x5F00;发功能。这就是我们所谓的 Appium“生态系统”。
Appium 团队确实自己维护了一些驱动程序(例如,我们之前讨论的 XCUITest 驱动程序)。 但它无法拥有特定于平台的专业知识或维护许多不同平台的驱动程序的能力。 但我们所做的,特别是从 Appium 2 开始,是提供工具来授权社区加入我们的愿景:
Appium 团队确实自己维护了一些驱动程序(例如,我们之前讨论的 XCUITest 驱动程序)。但它无法拥有特定于平台的专业知识或维护许多不同平台的驱动程序的能力。但我们所做的,特别是从 Appium 2 开始,是提供工具来授权社区加入我们的愿景:
- 任何人都可以通过创建一个符合适当约定并实现 WebDriver 协议的任何(子|超)集的 Node.js 模块来创建驱动程序。 创建驱动程序通常涉及最少的代码,因为 WebDriver 协议细节被抽象化,许多帮助库可用---这些库为 Appium 团队自己的驱动程序提供动力。
- 使用 Appium 驱动程序 CLI 与他人分享驱动程序很容易。 没有中央权威。
任何人都可以公开或私下分享驱动程序,免费或收费。 驱动程序可以是开源或闭源的(尽管我们欣赏开源!)。
- 任何人都可以通过创建一个符合适当约定并实现 WebDriver 协议的任何(子|超)集的 Node.js 模块来创建驱动程序。创建驱动程序通常涉及最少的代码,因为 WebDriver 协议细节被抽象化,许多帮助库可用---这些库为 Appium 团队自己的驱动程序提供动力。
- 使用 Appium 驱动程序 CLI 与他人分享驱动程序很容易。没有中央权威。任何人都可以公开或私下分享驱动程序,免费或收费。驱动程序可以是开源或闭源的(尽管我们欣赏开源!)。
Appium 作为开发平台的愿景超越了对所有应用平台自动化支持。 作为一个流行的自动化工具,有许多机会将 Appium 与各种其他工具和服务集成。 此外,Appium 有许多功能想法,要么作为核心服务器,要么在其各种驱动程序中的化身,核心团队没有足够的时间构建。 因此,Appium 2 发布了一个插件系统,使任何人都可以构建和分享改变 Appium 工作方式的模块!
Appium 作为开发平台的愿景超越了对所有应用平台自动化支持。作为一个流行的自动化工具,有许多机会将 Appium 与各种其他工具和服务集成。此外,Appium 有许多功能想法,要么作为核心服务器,要么在其各种驱动程序中的化身,核心团队没有足够的时间构建。因此,Appium 2 发布了一个插件系统,使任何人都可以构建和分享改变 Appium 工作方式的模块!
就像驱动程序可以通过 Appium 驱动程序 CLI 轻松分享和消费一样,插件可以通过[插件 CLI](../reference/cli/extensions.md)发布和消费。 插件可以做各种事情,例如添加 Appium 根据模板图像查找和与屏幕区域交互的能力(例如[图像插件](https://github.com/appium/appium/tree/master/packages/images-plugin))。 插件几乎没有限制,所以您可能也对学习如何在 Node.js 中[构建插件](../developing/build-plugins.md)以与 Appium 一起使用感兴趣。
就像驱动程序可以通过 Appium 驱动程序 CLI 轻松分享和消费一样,插件可以通过[插件 CLI](../reference/cli/extensions.md)发布和消费。插件可以做各种事情,例如添加 Appium 根据模板图像查找和与屏幕区域交互的能力(例如[图像插件](https://github.com/appium/appium/tree/master/packages/images-plugin))。插件几乎没有限制,所以您可能也对学习如何在 Node.js 中[构建插件](../developing/build-plugins.md)以与 Appium 一起使用感兴趣。
所以这就是 Appium:一个可扩展的、潜在一切的 UI 自动化通用接口! 继续阅读一些具体的介绍文档以获取更多细节,或查看各种指南以深入了解 Appium 的一些更一般概念和功能。
所以这就是 Appium:一个可扩展的、潜在一切的 UI 自动化通用接口!继续阅读一些具体的介绍文档以获取更多细节,或查看各种指南以深入了解 Appium 的一些更一般概念和功能。
+9 -10
View File
@@ -5,7 +5,7 @@ hide:
title: Appium 客户端简介
---
出于[概述](./appium.md)中讨论的所有原因,Appium 基于 [W3C WebDriver 规范](https://w3c.github.io/webdriver/)。 这意味着 Appium 实现了客户端-服务器架构。 服务器(由 Appium 本身以及您用于自动化的任何驱动程序或插件组成)连接到测试设备,并实际负责在这些设备上实现自动化。 客户端(&#x7531;_&#x60A8;_&#x9A71;动,Appium 测试作者)负责通过网络向服务器发送命令,并接收来自服务器的响应。 这些响应可用于判断自动化命令是否成功,或可能包含您查询的关于应用程序状态的信息。 本文档是对客户端方面的概念性介绍。
出于[概述](./appium.md)中讨论的所有原因,Appium 基于 [W3C WebDriver 规范](https://w3c.github.io/webdriver/)。这意味着 Appium 实现了客户端-服务器架构。服务器(由 Appium 本身以及您用于自动化的任何驱动程序或插件组成)连接到测试设备,并实际负责在这些设备上实现自动化。客户端(&#x7531;_&#x60A8;_&#x9A71;动,Appium 测试作者)负责通过网络向服务器发送命令,并接收来自服务器的响应。这些响应可用于判断自动化命令是否成功,或可能包含您查询的关于应用程序状态的信息。本文档是对客户端方面的概念性介绍。
!!! info
@@ -13,18 +13,18 @@ title: Appium 客户端简介
有关服务器端的信息(即 Appium 如何实际控制设备?),请查看我们的 [Appium 驱动程序简介](./drivers.md)。要跳转到 Appium 客户端库链接列表,请查看[客户端列表](../ecosystem/clients.md)。
```
有哪些自动化命令可用? 那取决于您在任何给定会话中使用的特定驱动程序和插件。 一组标准命令可能包括以下内容:
有哪些自动化命令可用?那取决于您在任何给定会话中使用的特定驱动程序和插件。一组标准命令可能包括以下内容:
- 查找元素
- 点击元素
- 获取页面源代码
- 截取屏幕截图
如果您查看 WebDriver 规范中的这些命令,您会注意到它们不是以任何特定编程语言定义的。 它们不是 Java 命令、JavaScript 命令或 Python 命令。 相反,它们形成了可以通&#x8FC7;_&#x4EFB;&#x4F55;_&#x7F16;程语言访问的 HTTP API( 如果您愿意,您可以只使用 cURL)。
如果您查看 WebDriver 规范中的这些命令,您会注意到它们不是以任何特定编程语言定义的。它们不是 Java 命令、JavaScript 命令或 Python 命令。相反,它们形成了可以通&#x8FC7;_&#x4EFB;&#x4F55;_&#x7F16;程语言访问的 HTTP API(如果您愿意,您可以只使用 cURL)。
例如,`查找元素`命令对应于发送到 HTTP 端点 `/session/:sessionid/element` 的 HTTP `POST` 请求(其中`:sessionid` 是服务器在之前 `创建会话` 调用中生成的唯一会话 ID 的占位符)。
此信息主要对开发与 WebDriver 规范一起工作的技术的人有用。 它对试图编写 Appium 或 Selenium 测试的人不是特别有用。 当您编写 Appium 测试时,您希望使用您熟悉的编程语言。 幸运的是,存在一组 [Appium 客户端库](../ecosystem/clients.md)[^1],它们负责向 Appium 服务器发起HTTP 的请求。 它们为特定编程语言公开了一组“原生”命令,以便对测试作者来说,它就像您在编写 Python、JavaScript 或 Java。
此信息主要对开发与 WebDriver 规范一起工作的技术的人有用。它对试图编写 Appium 或 Selenium 测试的人不是特别有用。当您编写 Appium 测试时,您希望使用您熟悉的编程语言。幸运的是,存在一组 [Appium 客户端库](../ecosystem/clients.md)[^1],它们负责向 Appium 服务器发起HTTP 的请求。它们为特定编程语言公开了一组“原生”命令,以便对测试作者来说,它就像您在编写 Python、JavaScript 或 Java。
以下是使用 Appium 客户端在五种不同编程语言中实现相同的 Appium 命令集的示例(注意,这不是包含所有导入的工作示例代码;请参阅每个客户端库的设置和命令参考说明):
@@ -85,16 +85,15 @@ System.Console.WriteLine(driver.PageSource);
尽管这些脚本使用不同的编程语言,但它们在底层做的事情是相同的:
1. 调用 `Find Element`,使用 `using` 参数值为 `xpath`,以及 `value` 参数表达用于查找元素的 XPath 查询。 (如果你对这些术语感到困惑,可以参考 Appium 或 Selenium 的入门介绍)
1. 调用 `Find Element`,使用 `using` 参数值为 `xpath`,以及 `value` 参数表达用于查找元素的 XPath 查询。(如果你对这些术语感到困惑,可以参考 Appium 或 Selenium 的入门介绍)
2. 使用在上一次调用中找到的元素 ID 调用 `Click Element`。
3. 使用同一元素的 ID 调用 `Get Element Text`,并将其打印到控制台。
4. 调用 `Get Page Source` 来检索页面/应用程序源代码并将其打印到控制台。
在选择或使用客户端之前,需要记住的另一件事是每个客户端都是独立维护的。 仅仅因为某个功能在一个客户端中可用,并不意味着它在另一个客户端中也可用(尽管所有客户端至少支持标准的 W3C 协议以及任何常见的 Appium 扩展)。 仅仅因为一个客户端有一套很好的辅助函数,并不意味着另一个客户端也会有。 有些客户端更新非常频繁,而其他客户端则不然! 因此,在考虑选择一个库时,首要考虑的是你想使用的语言,第二个考虑因素是该库的功能完善程度和维护情况!
在选择或使用客户端之前,需要记住的另一件事是每个客户端都是独立维护的。仅仅因为某个功能在一个客户端中可用,并不意味着它在另一个客户端中也可用(尽管所有客户端至少支持标准的 W3C 协议以及任何常见的 Appium 扩展)。仅仅因为一个客户端有一套很好的辅助函数,并不意味着另一个客户端也会有。有些客户端更新非常频繁,而其他客户端则不然! 因此,在考虑选择一个库时,首要考虑的是你想使用的语言,第二个考虑因素是该库的功能完善程度和维护情况!
要学习如何使用 Appium 客户端,请访问该客户端的主页以了解更多信息。 在许多情况下,特定语言的 Appium 客户端是在该语言的 _Selenium_ 客户端之上构建的,因此某些 Appium 客户端可能只记录 Appium 客户端在 Selenium 客户端基础上添加的功能。 也就是说,要获得完整的参考,你可能需要访问 Appium 客户端文档以及 Selenium 客户端文档。
要学习如何使用 Appium 客户端,请访问该客户端的主页以了解更多信息。在许多情况下,特定语言的 Appium 客户端是在该语言的 _Selenium_ 客户端之上构建的,因此某些 Appium 客户端可能只记录 Appium 客户端在 Selenium 客户端基础上添加的功能。也就是说,要获得完整的参考,你可能需要访问 Appium 客户端文档以及 Selenium 客户端文档。
这就是你需要了解的关于 Appium 客户端的全部内容! 前往 [客户端](../ecosystem/clients.md) 页面查看当前的客户端列表。
这就是你需要了解的关于 Appium 客户端的全部内容!前往 [客户端](../ecosystem/clients.md) 页面查看当前的客户端列表。
[^1]: 这些库也被称为"客户端"、"客户端库"或"客户端绑定"。
它们的意思都是一样的!
[^1]: 这些库也被称为"客户端"、"客户端库"或"客户端绑定"。它们的意思都是一样的!
+20 -21
View File
@@ -2,13 +2,13 @@
title: Appium 驱动程序介绍
---
正如[概览](./appium.md)所阐明的那样,"驱动程序"基本上是 Appium 对"我们如何支持多个不相关平台的自动化?"这一问题的答案。 在本文档中,我们将更详细地了解驱动程序的工作原理。 驱动程序工作的具体细节对你来说可能并不那么重要,除非你打算编写自己的驱动程序或为现有驱动程序做贡献(我们希望你这样做!)。
正如[概览](./appium.md)所阐明的那样,"驱动程序"基本上是 Appium 对"我们如何支持多个不相关平台的自动化?"这一问题的答案。在本文档中,我们将更详细地了解驱动程序的工作原理。驱动程序工作的具体细节对你来说可能并不那么重要,除非你打算编写自己的驱动程序或为现有驱动程序做贡献(我们希望你这样做!)。
了解更多驱动程序工作原理的主要好处是,了解典型的复杂性或典型的驱动程序架构将有助于你在测试中遇到问题时进行调试。
## 接口实现
在最基本的层面上,驱动程序只是扩展了 Appium 中包含的一个特殊类的 Node.js 类,这个类叫做 `BaseDriver`。 你可以用以下非常简单的几行代码就拥有一个非常接近"可工作"的驱动程序:
在最基本的层面上,驱动程序只是扩展了 Appium 中包含的一个特殊类的 Node.js 类,这个类叫做 `BaseDriver`。你可以用以下非常简单的几行代码就拥有一个非常接近"可工作"的驱动程序:
```js
import BaseDriver from '@appium/base-driver'
@@ -19,9 +19,9 @@ class MyNewDriver extends BaseDriver {
这个空驱动程序不&#x4F1A;_&#x505A;_&#x4EFB;何事情,但你可以将它打包成一个 Node.js 模块,在模块的清单文件(`package.json`)中添加一些与 Appium 相关的字段,然后使用 `appium driver install` 安装它。
因此,从技术角度来看,Appium 驱动程序只是一些继承自其他 Appium 代码的代码。 就是这样! 现在,继承自 `BaseDriver` 实际上给了我们很多东西,因为 `BaseDriver` 本质上是整个 WebDriver 协议的封装。 所以驱动程序要做一些有用的事情,只需&#x8981;_&#x5B9E;&#x73B0;_&#x540D;称与其 WebDriver 协议等效的 Node.js 方法。
因此,从技术角度来看,Appium 驱动程序只是一些继承自其他 Appium 代码的代码。就是这样!现在,继承自 `BaseDriver` 实际上给了我们很多东西,因为 `BaseDriver` 本质上是整个 WebDriver 协议的封装。所以驱动程序要做一些有用的事情,只需&#x8981;_&#x5B9E;&#x73B0;_&#x540D;称与其 WebDriver 协议等效的 Node.js 方法。
假设我想用这个空驱动程序做点什么;首先我必须决定要实现哪个 WebDriver 命令。 对于我们的例子,让我们选择[Navigate To](https://w3c.github.io/webdriver/#navigate-to) WebDriver 命令。 暂时不考虑当这个命令执行时我想让驱动程&#x5E8F;_&#x505A;_&#x4EC0;么。 要告诉 Appium 驱动程序可以处理这个命令,我们所要做的就是在驱动程序类中定义一个像这样的方法:[^1]
假设我想用这个空驱动程序做点什么;首先我必须决定要实现哪个 WebDriver 命令。对于我们的例子,让我们选择[Navigate To](https://w3c.github.io/webdriver/#navigate-to) WebDriver 命令。暂时不考虑当这个命令执行时我想让驱动程&#x5E8F;_&#x505A;_&#x4EC0;么。要告诉 Appium 驱动程序可以处理这个命令,我们所要做的就是在驱动程序类中定义一个像这样的方法:[^1]
```js
async setUrl(url) {
@@ -29,10 +29,9 @@ async setUrl(url) {
}
```
[^1]: 你可能会注意到 `setUrl` 看起来与 `Navigate To` 完全不同,那么我们怎么知道使用它而不是其他随机字符串呢? 其实,Appium 的 WebDriver 协议到方法名的映射是在 `@appium/base-driver` 包中的一个特殊文件中定义的,叫做 [routes.js](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/routes.js)。
所以如果你正在编写驱动程序,这就是你要去找出要使用什么方法名和期望什么参数的地方。 或者你可以查看任何主要 Appium 驱动程序的源代码!
[^1]: 你可能会注意到 `setUrl` 看起来与 `Navigate To` 完全不同,那么我们怎么知道使用它而不是其他随机字符串呢?其实,Appium 的 WebDriver 协议到方法名的映射是在 `@appium/base-driver` 包中的一个特殊文件中定义的,叫做 [routes.js](https://github.com/appium/appium/blob/master/packages/base-driver/lib/protocol/routes.js)。所以如果你正在编写驱动程序,这就是你要去找出要使用什么方法名和期望什么参数的地方。或者你可以查看任何主要 Appium 驱动程序的源代码!
就是这样! 我们如何实际实现该命令完全取决于我们自己,并取决于我们想要支持的平台。 以下是针对不同平台的此命令的一些不同示例实现:
就是这样!我们如何实际实现该命令完全取决于我们自己,并取决于我们想要支持的平台。以下是针对不同平台的此命令的一些不同示例实现:
- 浏览器:执行一些 JavaScript 来设置 `window.location.href`
- iOS 应用:使用深度链接启动应用
@@ -44,30 +43,30 @@ async setUrl(url) {
[^2]: 当然,我们希望保持语义尽可能相似,但在 iOS 的世界里,例如,通过深度链接(具有特殊应用特定方案的 URL)启动应用是我们最接近导航到 Web URL 的方式。
我们之所以如此详细地讨论(顺便说一下,你不需要记住这些),是因为重要的是要强调一点,Appium 驱动程序本质上不是任何特定的东西,只是一些可以处理 WebDriver 协议命令的 JS 代码。 从那里去哪里取决于你,驱动程序作者!
我们之所以如此详细地讨论(顺便说一下,你不需要记住这些),是因为重要的是要强调一点,Appium 驱动程序本质上不是任何特定的东西,只是一些可以处理 WebDriver 协议命令的 JS 代码。从那里去哪里取决于你,驱动程序作者!
## 自动化映射
&#x4F46;_&#x901A;&#x5E38;_&#x9A71;动程序作者想要做的是为给定的平台提供自动化行为,这些行为在语义上与浏览器的 WebDriver 规范实现非常相似。 当你想查找一个元素时,你应该得到一个 UI 元素的引用。 当你想点击或轻触该元素时,产生的行为应该与人点击或轻触该元素相同。 等等。
&#x4F46;_&#x901A;&#x5E38;_&#x9A71;动程序作者想要做的是为给定的平台提供自动化行为,这些行为在语义上与浏览器的 WebDriver 规范实现非常相似。当你想查找一个元素时,你应该得到一个 UI 元素的引用。当你想点击或轻触该元素时,产生的行为应该与人点击或轻触该元素相同。等等。
因此,驱动程序作者面临的真正挑战不是如何使用 WebDriver 协议(因为 `BaseDriver` 为你封装了所有这些),而是如何在目标平台上实现实际的自动化。 每个驱动程序都依赖于自己的一套底层技术。 正如[概览](index.md)中提到的,iOS 驱动程序使用 Apple 的一项名为 [XCUITest](https://developer.apple.com/documentation/xctest/xcuielement) 的技术。 这些底层自动化技术通常都有自己专有或特殊的 API。 编写驱动程序就是将 WebDriver 协议映射到这个底层 API 的任务(有时是一组不同的底层 API——例如,UiAutomator2 驱动程序不仅依赖于 Google 的 [UiAutomator2](https://developer.android.com/training/testing/other-components/ui-automator) 技术,还依赖于只能通过 [ADB](https://developer.android.com/tools/adb) 获得的功能,以及只能通过辅助应用内的 Android SDK 获得的功能)。 将所有这些整合到一个单一、可用的 WebDriver 接口中,是驱动程序开发这项非常有用(但极具挑战性)的艺术!
因此,驱动程序作者面临的真正挑战不是如何使用 WebDriver 协议(因为 `BaseDriver` 为你封装了所有这些),而是如何在目标平台上实现实际的自动化。每个驱动程序都依赖于自己的一套底层技术。正如[概览](index.md)中提到的,iOS 驱动程序使用 Apple 的一项名为 [XCUITest](https://developer.apple.com/documentation/xctest/xcuielement) 的技术。这些底层自动化技术通常都有自己专有或特殊的 API。编写驱动程序就是将 WebDriver 协议映射到这个底层 API 的任务(有时是一组不同的底层 API——例如,UiAutomator2 驱动程序不仅依赖于 Google 的 [UiAutomator2](https://developer.android.com/training/testing/other-components/ui-automator) 技术,还依赖于只能通过 [ADB](https://developer.android.com/tools/adb) 获得的功能,以及只能通过辅助应用内的 Android SDK 获得的功能)。将所有这些整合到一个单一、可用的 WebDriver 接口中,是驱动程序开发这项非常有用(但极具挑战性)的艺术!
## 多层架构
在实践中,这通常会导致相当复杂的架构。 让我们再次以 iOS 为例。
XCUITest 框架(Appium 驱动程序使用的框架)期望调用它的代码用 Objective-C 或 Swift 编写。 此外,XCUITest 代码只能在由 Xcode(以及直接或间接的 Xcode 命令行工具)触发的特殊模式下运行。 换句话说,没有直接的方法从 Node.js 函数实现(如上面的 `setUrl()`)到 XCUITest API 调用。
在实践中,这通常会导致相当复杂的架构。让我们再次以 iOS 为例。
XCUITest 框架(Appium 驱动程序使用的框架)期望调用它的代码用 Objective-C 或 Swift 编写。此外,XCUITest 代码只能在由 Xcode(以及直接或间接的 Xcode 命令行工具)触发的特殊模式下运行。换句话说,没有直接的方法从 Node.js 函数实现(如上面的 `setUrl()`)到 XCUITest API 调用。
XCUITest 驱动程序作者所做的是将驱动程序分成两部分:一部分用 Node.js 编写(这部分被整合到 Appium 中并最初处理 WebDriver 命令),另一部分用 Objective-C 编写(这部分实际在 iOS 设备上运行并进行 XCUITest API 调用)。 这使得与 XCUITest 的接口成为可能,但引入了两部分之间协调的新问题。
XCUITest 驱动程序作者所做的是将驱动程序分成两部分:一部分用 Node.js 编写(这部分被整合到 Appium 中并最初处理 WebDriver 命令),另一部分用 Objective-C 编写(这部分实际在 iOS 设备上运行并进行 XCUITest API 调用)。这使得与 XCUITest 的接口成为可能,但引入了两部分之间协调的新问题。
驱动程序作者本可以选择许多非常不同的策略来建模 Node.js 端和 Objective-C 端之间的通信,但最终决定使用……
WebDriver 协议! 没错,XCUITest 驱动程序的 Objective-C 端本身就是一个 WebDriver 实现,称为 [WebDriverAgent](https://github.com/appium/webdriveragent)。[^3]
WebDriver 协议!没错,XCUITest 驱动程序的 Objective-C 端本身就是一个 WebDriver 实现,称为 [WebDriverAgent](https://github.com/appium/webdriveragent)。[^3]
[^3]: 因此,理论上你可以将你的 WebDriver 客户端直接指向 WebDriverAgent 并完全绕过 Appium。 然而,这通常不方便,原因如下:
[^3]: 因此,理论上你可以将你的 WebDriver 客户端直接指向 WebDriverAgent 并完全绕过 Appium。然而,这通常不方便,原因如下:
- Appium XCUITest 驱动程序会为你构建和管理 WebDriverAgent,这可能很麻烦并涉及使用 Xcode。
- XCUITest 驱动程序做的事情远不止 WebDriverAgent 所能做的,例如使用模拟器或设备、安装应用等。
这个故事的寓意是,由于我们试图解决的问题的性质,驱动程序架构可能会变得相当复杂和多层。 这也意味着,如果你在特定测试中遇到问题,有时很难判断这一系列技术中的哪个环节出了问题。 再以 XCUITest 为例,我们同时使用以下一组技术:
这个故事的寓意是,由于我们试图解决的问题的性质,驱动程序架构可能会变得相当复杂和多层。这也意味着,如果你在特定测试中遇到问题,有时很难判断这一系列技术中的哪个环节出了问题。再以 XCUITest 为例,我们同时使用以下一组技术:
- 你的测试代码(使用其编程语言)- 由你拥有
- Appium 客户端库 - 由 Appium 拥有
@@ -85,17 +84,17 @@ WebDriver 协议! 没错,XCUITest 驱动程序的 Objective-C 端本身就
## 代理模式
还有一个关于驱动程序的重要架构方面需要了解。 可以再次用 XCUITest 驱动程序来举例说明。 回想一下我们刚才讨论的 XCUITest 驱动程序的两个"部分"是如何都使用 WebDriver 协议的——Node.js 部分直接连接到 Appium 的 WebDriver 服务器,而 Objective-C 部分(WebDriverAgent)本身就是一个 WebDriver 实现。
还有一个关于驱动程序的重要架构方面需要了解。可以再次用 XCUITest 驱动程序来举例说明。回想一下我们刚才讨论的 XCUITest 驱动程序的两个"部分"是如何都使用 WebDriver 协议的——Node.js 部分直接连接到 Appium 的 WebDriver 服务器,而 Objective-C 部分(WebDriverAgent)本身就是一个 WebDriver 实现。
这为 Appium 在某些情况下走捷径开辟了可能性。
让我们想象一下 XCUITest 驱动程序需要实现 `Click Element` 命令。 这个实现的内部代码看起来就像是获取适当的参数并构造一个到 WebDriverAgent 服务器的 HTTP 请求。 在这种情况下,我们基本上只是在重建客户端对 Appium 服务器的原始调用![^4] 所以实际上没有必要编写一个函数来实现 `Click Element` 命令。 相反,XCUITest 驱动程序可以让 Appium 知道这个命令应该直接代理到其他 WebDriver 服务器。
让我们想象一下 XCUITest 驱动程序需要实现 `Click Element` 命令。这个实现的内部代码看起来就像是获取适当的参数并构造一个到 WebDriverAgent 服务器的 HTTP 请求。在这种情况下,我们基本上只是在重建客户端对 Appium 服务器的原始调用![^4] 所以实际上没有必要编写一个函数来实现 `Click Element` 命令。相反,XCUITest 驱动程序可以让 Appium 知道这个命令应该直接代理到其他 WebDriver 服务器。
[^4]: 这不&#x662F;_&#x5B8C;&#x5168;_&#x76F8;同的调用,因为 Appium 服务器和 WebDriverAgent 服务器会生成不同的会话 ID,但这些差异会被透明地处理。
如果你不熟悉"代理"的概念,在这种情况下,它只是意味着 XCUITest 驱动程序根本不会参与处理该命令。 相反,它只会在协议级别被重新打包并转发到 WebDriverAgent,WebDriverAgent 的响应同样会直接传回客户端,而不会有任何 XCUITest 驱动程序代码看到或修改它。
如果你不熟悉"代理"的概念,在这种情况下,它只是意味着 XCUITest 驱动程序根本不会参与处理该命令。相反,它只会在协议级别被重新打包并转发到 WebDriverAgent,WebDriverAgent 的响应同样会直接传回客户端,而不会有任何 XCUITest 驱动程序代码看到或修改它。
这种架构模式为选择在各处使用 WebDriver 协议而不是构建定制协议的驱动程序作者提供了一个很好的额外好处。 这也意味着 Appium 可以非常轻松地为任何其他现有的 WebDriver 实现创建包装驱动程序。 例如,如果你查看 [Appium Safari 驱动程序](https://github.com/appium/appium-safari-driver)代码,你会发现它基本上没有实现任何标准命令,因为所有这些命令都直接代理到底层的 SafariDriver 进程。
这种架构模式为选择在各处使用 WebDriver 协议而不是构建定制协议的驱动程序作者提供了一个很好的额外好处。这也意味着 Appium 可以非常轻松地为任何其他现有的 WebDriver 实现创建包装驱动程序。例如,如果你查看 [Appium Safari 驱动程序](https://github.com/appium/appium-safari-driver)代码,你会发现它基本上没有实现任何标准命令,因为所有这些命令都直接代理到底层的 SafariDriver 进程。
了解这种代理机制有时在幕后发生是很重要的,因为如果你曾经深入研究一些开源驱动程序代码试图找出命令的实现位置,你可能会惊讶地发现 Node.js 驱动程序代码本身根本没有任何实现! 在这种情况下,你需要找出命令被代理到哪里,以便你可以在那里查找相应的实现。
了解这种代理机制有时在幕后发生是很重要的,因为如果你曾经深入研究一些开源驱动程序代码试图找出命令的实现位置,你可能会惊讶地发现 Node.js 驱动程序代码本身根本没有任何实现!在这种情况下,你需要找出命令被代理到哪里,以便你可以在那里查找相应的实现。
好了,对于这个非常详细的驱动程序介绍来说,这就足够了!
+19 -20
View File
@@ -2,71 +2,70 @@
title: Appium 项目历史
---
Appium 自 2012 年以来以某种形式存在。 它由不同的个人和组织指导,甚至用 3 种不同的编程语言实现过! 欢迎了解关于 Appium 如何发展到今天这个样子的更多信息……
Appium 自 2012 年以来以某种形式存在。它由不同的个人和组织指导,甚至用 3 种不同的编程语言实现过!欢迎了解关于 Appium 如何发展到今天这个样子的更多信息……
## 早期灵感
[Dan Cuellar](https://twitter.com/thedancuellar) 在 2011 年担任 Zoosk 的测试经理时遇到了一个问题。 iOS 产品的测试通过时间变得越来越长,难以控制。 减少测试是一个选择,但会带来额外的风险,特别是在通过 iOS App Store 审核流程获得补丁需要几天时间的情况下。 他回想起自己在网站上工作的日子,意识到自动化是答案。
[Dan Cuellar](https://twitter.com/thedancuellar) 在 2011 年担任 Zoosk 的测试经理时遇到了一个问题。 iOS 产品的测试通过时间变得越来越长,难以控制。减少测试是一个选择,但会带来额外的风险,特别是在通过 iOS App Store 审核流程获得补丁需要几天时间的情况下。他回想起自己在网站上工作的日子,意识到自动化是答案。
Dan 调查了现有的工具格局,却发现它们都有重大缺陷。 Apple 提供的工具 UIAutomation 要求测试必须用 JavaScript 编写,并且不允许实时调试或解释。 它还必须在 Xcode 的性能分析工具 Instruments 中执行。 其他第三方工具使用私有 API,并要求将 SDK 和 HTTP 服务器嵌入到应用程序中。 这看起来非常不可取。
Dan 调查了现有的工具格局,却发现它们都有重大缺陷。 Apple 提供的工具 UIAutomation 要求测试必须用 JavaScript 编写,并且不允许实时调试或解释。它还必须在 Xcode 的性能分析工具 Instruments 中执行。其他第三方工具使用私有 API,并要求将 SDK 和 HTTP 服务器嵌入到应用程序中。这看起来非常不可取。
对现有选项不满意,Dan 向他的经理要求一些额外的时间,看看他能否找到更好的方法。 他花了 2 周时间探索和试验,看看是否有办法使用 Apple 批准的技术来自动化 iOS 应用程序。 他尝试的第一个实现使用 AppleScript 通过 OS X 辅助功能 API 向 Mac UI 元素发送消息。 这在一定程度上有效,但永远无法在真实设备上工作,更不用说其他缺点了。
对现有选项不满意,Dan 向他的经理要求一些额外的时间,看看他能否找到更好的方法。他花了 2 周时间探索和试验,看看是否有办法使用 Apple 批准的技术来自动化 iOS 应用程序。他尝试的第一个实现使用 AppleScript 通过 OS X 辅助功能 API 向 Mac UI 元素发送消息。这在一定程度上有效,但永远无法在真实设备上工作,更不用说其他缺点了。
所以他想,如果我能让 UIAutomation 框架像解释器一样实时运行会怎样? 他研究了一下,确定他所需要做的就是找到一种方法在 UIAutomation JavaScript 程序中接收、执行和回复命令。 使用 Apple 提供的用于执行 shell 命令的实用程序,他能够 `cat` 顺序排列的文本文件来接收命令,`eval()` 输出以执行它们,并用 `python` 将它们写回磁盘。 然后他用 C# 准备了实现 Selenium 样式语法的代码来编写顺序排列的 JavaScript 命令。
所以他想,如果我能让 UIAutomation 框架像解释器一样实时运行会怎样?他研究了一下,确定他所需要做的就是找到一种方法在 UIAutomation JavaScript 程序中接收、执行和回复命令。使用 Apple 提供的用于执行 shell 命令的实用程序,他能够 `cat` 顺序排列的文本文件来接收命令,`eval()` 输出以执行它们,并用 `python` 将它们写回磁盘。然后他用 C# 准备了实现 Selenium 样式语法的代码来编写顺序排列的 JavaScript 命令。
iOSAuto 诞生了。
## 2012 年 Selenium 大会
Dan 被选中在 2012 年伦敦 Selenium 大会上就一个完全不同的主题发表演讲。 作为他演讲的一部分,他展示了使用 Selenium 语法的 iOS 自动化,以演示编写平台无关的测试,这些测试使用具有通用接口的独立平台特定页面对象。 令他惊讶的是,酷炫的测试架构让 iOS 测试像 WebDriver 测试一样运行。 有几个人建议他稍后在会议上做一个闪电演讲来解释它是如何工作的。
Dan 被选中在 2012 年伦敦 Selenium 大会上就一个完全不同的主题发表演讲。作为他演讲的一部分,他展示了使用 Selenium 语法的 iOS 自动化,以演示编写平台无关的测试,这些测试使用具有通用接口的独立平台特定页面对象。令他惊讶的是,酷炫的测试架构让 iOS 测试像 WebDriver 测试一样运行。有几个人建议他稍后在会议上做一个闪电演讲来解释它是如何工作的。
在会议的第二天,Dan 走上舞台做闪电演讲。 Jason Huggins,Selenium 的共同创建者,主持了闪电演讲。 Dan 在加载演示文稿时遇到了技术困难,Jason 几乎不得不继续下一个闪电演讲。 在最后一刻,屏幕亮了,Dan 开始了他的演讲。 他解释了他的实现细节和工作原理,恳求贡献者,五分钟后就结束了。 观众礼貌地鼓掌,他离开了舞台。
在会议的第二天,Dan 走上舞台做闪电演讲。 Jason Huggins,Selenium 的共同创建者,主持了闪电演讲。 Dan 在加载演示文稿时遇到了技术困难,Jason 几乎不得不继续下一个闪电演讲。在最后一刻,屏幕亮了,Dan 开始了他的演讲。他解释了他的实现细节和工作原理,恳求贡献者,五分钟后就结束了。观众礼貌地鼓掌,他离开了舞台。
## 电话响起
Selenium 大会四个月后,Jason 打电话给 Dan。 Jason 一直在 Sauce Labs 为客户开发 iOS 测试支持。 Jason 记得 Dan 的闪电演讲,认为这个项目可能对 Jason 的工作有用,但 Dan 的源代码没有公开。 Jason 要求 Dan 见面。 那周晚些时候,Dan 在旧金山的一家酒吧见到了 Jason,并向他展示了 iOS Auto 的源代码。
Selenium 大会四个月后,Jason 打电话给 Dan。 Jason 一直在 Sauce Labs 为客户开发 iOS 测试支持。 Jason 记得 Dan 的闪电演讲,认为这个项目可能对 Jason 的工作有用,但 Dan 的源代码没有公开。 Jason 要求 Dan 见面。那周晚些时候,Dan 在旧金山的一家酒吧见到了 Jason,并向他展示了 iOS Auto 的源代码。
作为一个长期的开源倡导者,Jason 鼓励 Dan 在开源许可下发布他的代码。 8 月,Dan 在 GitHub 上发布了 [C# 的源代码](https://github.com/penguinho/appium-old/commit/3ab56d3a5601897b2790b5256351f9b5af3f9e90)。 Jason 鼓励 Dan 更改语言以使项目对潜在贡献者更具吸引力。 Dan 上传了 [Python 的新版本]](https://github.com/penguinho/appium-old/commit/9b891207be0957bf209a77242750da17d3eb8eda)。
9 月,Jason 添加了一个 Web 服务器并开始通过 HTTP 实现 [WebDriver 协议](https://github.com/hugs/appium-old/commit/ae8fe4578640d9af9137d0546190fa29317d1499),使 iOS Auto 可以从任何语言的任何 Selenium WebDriver 客户端库编写脚本。
## 移动测试峰会
Jason 决定该项目应该在 11 月的[移动测试峰会](https://twitter.com/mobtestsummit)上展示,但建议项目首先改个新名字。 人们提出了许多想法,他们最终选定了 AppleCart。 一天后,当 Jason 浏览 Apple 关于版权和商标的一些指导时,他注意到在 Apple 会保护其商标的名称示例部分下,第一个例子是"AppleCart"。 他打电话给 Dan 并告知了情况,他们集思广益了一会儿,然后 Jason 中了大奖。 Appium…… 应用程序的 Selenium。
Jason 决定该项目应该在 11 月的[移动测试峰会](https://twitter.com/mobtestsummit)上展示,但建议项目首先改个新名字。人们提出了许多想法,他们最终选定了 AppleCart。一天后,当 Jason 浏览 Apple 关于版权和商标的一些指导时,他注意到在 Apple 会保护其商标的名称示例部分下,第一个例子是"AppleCart"。他打电话给 Dan 并告知了情况,他们集思广益了一会儿,然后 Jason 中了大奖。 Appium……应用程序的 Selenium。
## Sauce Labs 和 Node.js
2013 年 1 月,也就是移动测试峰会后不久,Sauce Labs 决定全力支持 Appium 并提供更多开发人员力量。 创建了一个工作组来评估当前状态以及如何最好地推进该项目。
该团队包括 Jonathan Lipps(现任项目负责人),他们决定 Appium 需要重生,并最终选择 Node.js 作为使用的框架。 Node 以快速高效的 Web 服务器后端而闻名,而归根结底,Appium 只是一个高度专业化的 Web 服务器。 还决定将 JavaScript 作为一种语言足够易于访问,使得 Appium 能够发展成为一个比其他选项更大的开源开发者社区。
2013 年 1 月,也就是移动测试峰会后不久,Sauce Labs 决定全力支持 Appium 并提供更多开发人员力量。创建了一个工作组来评估当前状态以及如何最好地推进该项目。该团队包括 Jonathan Lipps(现任项目负责人),他们决定 Appium 需要重生,并最终选择 Node.js 作为使用的框架。 Node 以快速高效的 Web 服务器后端而闻名,而归根结底,Appium 只是一个高度专业化的 Web 服务器。还决定将 JavaScript 作为一种语言足够易于访问,使得 Appium 能够发展成为一个比其他选项更大的开源开发者社区。
仅仅几天之内,团队就利用 Appium 的现有工作,创建了一个与之前 Python 版本功能相当的新版本 Appium。 为 Appium 的基本架构奠定了基础,从那时起我们一直在成功地构建它。 在这次冲刺开始几周后,Jonathan Lipps 被正式指定为项目负责人,他开始制定战略,如何让更多来自社区的人参与 Appium 的开发。
仅仅几天之内,团队就利用 Appium 的现有工作,创建了一个与之前 Python 版本功能相当的新版本 Appium。为 Appium 的基本架构奠定了基础,从那时起我们一直在成功地构建它。在这次冲刺开始几周后,Jonathan Lipps 被正式指定为项目负责人,他开始制定战略,如何让更多来自社区的人参与 Appium 的开发。
## Appium 走向世界
最终,Jonathan 决定在会议和聚会上让尽可能多的开发者了解 Appium 是吸引用户和贡献的最佳方式。
Appium 的新版本在 [2013 年谷歌测试自动化大会](https://www.youtube.com/watch?v=1J0aXDbjiUE)上首次亮相。 2013 年晚些时候,Appium 在美国各地以及英国、波兰、葡萄牙和澳大利亚的会议和聚会上展示。 值得注意的是,Jonathan 让 Appium [作为乐队中的乐器表演](https://www.youtube.com/watch?v=zsbNVkayYRQ),Dan Cuellar 为 Selenium 大会制作了一个有趣的 [Appium 视频集锦](https://www.youtube.com/watch?v=xkzrEn0v0II)。
Appium 的新版本在 [2013 年谷歌测试自动化大会](https://www.youtube.com/watch?v=1J0aXDbjiUE)上首次亮相。 2013 年晚些时候,Appium 在美国各地以及英国、波兰、葡萄牙和澳大利亚的会议和聚会上展示。值得注意的是,Jonathan 让 Appium [作为乐队中的乐器表演](https://www.youtube.com/watch?v=zsbNVkayYRQ),Dan Cuellar 为 Selenium 大会制作了一个有趣的 [Appium 视频集锦](https://www.youtube.com/watch?v=xkzrEn0v0II)。
在所有这些演讲和会议期间,项目继续发展。 2013 年初,我们发布了 Android 和 Selendroid 支持,使 Appium 成为第一个真正的跨平台自动化框架。 该项目还继续吸引用户和贡献者,到 2013 年底,我们已经有超过 1,000 次提交。
在所有这些演讲和会议期间,项目继续发展。 2013 年初,我们发布了 Android 和 Selendroid 支持,使 Appium 成为第一个真正的跨平台自动化框架。该项目还继续吸引用户和贡献者,到 2013 年底,我们已经有超过 1,000 次提交。
## 通往 Appium 1.0 之路
Appium 开始显著增长和成熟。 2014 年 5 月,我们发布了 Appium 1.0,这是 Appium 发展的一个里程碑。
Appium 获得了[各种](https://www.prnewswire.com/news-releases/black-duck-announces-black-duck-open-source-rookies-of-the-year-winners-242383341.html)[奖项](https://www.infoworld.com/article/2241247/164642-bossie-awards-2014-the-best-open-source-application-development-tools.html),并成为最受欢迎的开源跨平台移动自动化框架。 稳定性得到改善,错误得到优先处理和修复,功能得到添加。 Sauce Labs 增加了捐赠给 Appium 工作的开发者数量,但整个社区继续参与指导项目并为其做出贡献,项目治理继续在公开的邮件列表和 GitHub 的问题跟踪器上进行。
Appium 获得了[各种](https://www.prnewswire.com/news-releases/black-duck-announces-black-duck-open-source-rookies-of-the-year-winners-242383341.html)[奖项](https://www.infoworld.com/article/2241247/164642-bossie-awards-2014-the-best-open-source-application-development-tools.html),并成为最受欢迎的开源跨平台移动自动化框架。稳定性得到改善,错误得到优先处理和修复,功能得到添加。 Sauce Labs 增加了捐赠给 Appium 工作的开发者数量,但整个社区继续参与指导项目并为其做出贡献,项目治理继续在公开的邮件列表和 GitHub 的问题跟踪器上进行。
## Appium 扩大
最终,很明显 Appium 代码库没有针对大型分布式团队的兼职贡献者进行优化。 作为提交者团队,我们借此机会从头开始重写 Appium,使用更现代的 JavaScript 语言版本,并重做 Appium 的架构,使用户或第三方开发者可以轻松构建自己的 Appium"驱动程序"。 我们希望新贡献者更容易熟悉 Appium 代码库,并看到核心团队以外的团队为 Appium 添加对新平台的支持。 这一愿景已经开始实现,微软和 Youi.tv 等团体分别为 Windows 桌面应用程序自动化和 Youi.tv 应用程序自动化向 Appium 添加了驱动程序。
最终,很明显 Appium 代码库没有针对大型分布式团队的兼职贡献者进行优化。作为提交者团队,我们借此机会从头开始重写 Appium,使用更现代的 JavaScript 语言版本,并重做 Appium 的架构,使用户或第三方开发者可以轻松构建自己的 Appium"驱动程序"。我们希望新贡献者更容易熟悉 Appium 代码库,并看到核心团队以外的团队为 Appium 添加对新平台的支持。这一愿景已经开始实现,微软和 Youi.tv 等团体分别为 Windows 桌面应用程序自动化和 Youi.tv 应用程序自动化向 Appium 添加了驱动程序。
## Appium 献给人民
2016 年底,Sauce Labs 将 Appium 作为一个项目捐赠给 [JS 基金会](https://js.foundation),以便向世界证明 Sauce 承诺 Appium 保持开源。 JS 基金会是一个非营利性开源管理组织,负责持有开源项目的版权,并确保它们在社区中拥有长久和成功的任期。 由于我们转移到非营利基金会,我们希望新贡献者的大门会更加敞开,无论是作为个人还是代表现在对 Appium 前进有兴趣的众多公司之一。
2016 年底,Sauce Labs 将 Appium 作为一个项目捐赠给 [JS 基金会](https://js.foundation),以便向世界证明 Sauce 承诺 Appium 保持开源。 JS 基金会是一个非营利性开源管理组织,负责持有开源项目的版权,并确保它们在社区中拥有长久和成功的任期。由于我们转移到非营利基金会,我们希望新贡献者的大门会更加敞开,无论是作为个人还是代表现在对 Appium 前进有兴趣的众多公司之一。
最终,JS 基金会并入了 [OpenJS 基金会](https://openjsf.org),Appium 成为该基金会的影响力项目。
## Appium 2.0
Appium 2 于 2023 年发布,引入了一个全新的架构,将重点从一体化项目转移到作为生态系统的 Appium。 这为任何人开发和共享自己的 Appium 扩展(驱动程序和插件)开辟了可能性,为远超 iOS 和 Android 的平台的自动化相关开发打开了一个充满可能性的世界! 因此,创建了许多第三方扩展,例如用于 Flutter 和 Windows 的新驱动程序、用于模拟 API 和管理设备农场的插件、基于 Rust 和 Swift 的新 Appium 客户端等等。
Appium 2 于 2023 年发布,引入了一个全新的架构,将重点从一体化项目转移到作为生态系统的 Appium。这为任何人开发和共享自己的 Appium 扩展(驱动程序和插件)开辟了可能性,为远超 iOS 和 Android 的平台的自动化相关开发打开了一个充满可能性的世界!因此,创建了许多第三方扩展,例如用于 Flutter 和 Windows 的新驱动程序、用于模拟 API 和管理设备农场的插件、基于 Rust 和 Swift 的新 Appium 客户端等等。
大约在这个时候,我们还启动了 Appium 的赞助计划,吸引了各种大小赞助商。 这使我们能够回馈 Appium 社区,为贡献者在项目上的志愿工作提供补偿。
大约在这个时候,我们还启动了 Appium 的赞助计划,吸引了各种大小赞助商。这使我们能够回馈 Appium 社区,为贡献者在项目上的志愿工作提供补偿。
## Appium 3.0
2025 年看到了 Appium 3 的发布。 这次更新比 Appium 2 小得多,只包括一些行为变化,而是专注于删除已弃用的代码并更新对更现代生态系统的兼容性。 尽管如此,这种缩小的范围也是意料之中的:自 Appium 2 以来,主要功能开发工作已转移到各个驱动程序和插件,其中许多在 Appium 2 时代经历了多次重大更新。
2025 年看到了 Appium 3 的发布。这次更新比 Appium 2 小得多,只包括一些行为变化,而是专注于删除已弃用的代码并更新对更现代生态系统的兼容性。尽管如此,这种缩小的范围也是意料之中的:自 Appium 2 以来,主要功能开发工作已转移到各个驱动程序和插件,其中许多在 Appium 2 时代经历了多次重大更新。
+2 -2
View File
@@ -5,7 +5,7 @@ hide:
title: Appium 概览
---
如主页所述,Appium 旨在支&#x6301;_&#x591A;个不同平台_(移动端、Web、桌面等)的 UI 自动化。 不仅如此,它还旨在支持&#x7528;_&#x4E0D;同语言_(JS、Java、Python 等)编写的自动化代码。 将所有这些功能整合到一个程序中是一项非常艰巨的任务,甚至可以说是不可能完成的任务!
如主页所述,Appium 旨在支&#x6301;_&#x591A;个不同平台_(移动端、Web、桌面等)的 UI 自动化。不仅如此,它还旨在支持&#x7528;_&#x4E0D;同语言_(JS、Java、Python 等)编写的自动化代码。将所有这些功能整合到一个程序中是一项非常艰巨的任务,甚至可以说是不可能完成的任务!
为了实现这一目标,Appium 实际上被分为四个部分:
@@ -25,7 +25,7 @@ title: Appium 概览
- 为你的目标编程语言安装客户端库
- (可选)安装一个或多个插件
这些就是基础知识! 如果你已经准备好开始,请继续阅读[快速入门](../quickstart/index.md)!
这些就是基础知识!如果你已经准备好开始,请继续阅读[快速入门](../quickstart/index.md)!
如果你想了解更多关于其工作原理的详细信息,请参阅以下页面的背景材料:
+3 -3
View File
@@ -5,7 +5,7 @@ hide:
title: 入门指南
---
让我们开始使用Appium! 为了成功使用此快速入门,建议您首先阅读[介绍](../intro/index.md),以便了解运行Appium和编写Appium脚本的概念。
让我们开始使用Appium!为了成功使用此快速入门,建议您首先阅读[介绍](../intro/index.md),以便了解运行Appium和编写Appium脚本的概念。
此快速入门的基本计划如下:
@@ -18,6 +18,6 @@ title: 入门指南
### 系统需求
在开始之前,请确保您的系统满足运行Appium服务器的[要求](../quickstart/requirements.md)。 其他要求将在安装UiAutomator2驱动程序时讨论。 本指南还假设您在平台上具有基本的命令行熟练度,例如能够运行命令、设置和持久化环境变量等...
在开始之前,请确保您的系统满足运行Appium服务器的[要求](../quickstart/requirements.md)。其他要求将在安装UiAutomator2驱动程序时讨论。本指南还假设您在平台上具有基本的命令行熟练度,例如能够运行命令、设置和持久化环境变量等...
现在您准备开始了! 前往安装[Appium](./install.md)。
现在您准备开始了!前往安装[Appium](./install.md)。
@@ -31,8 +31,7 @@ Appium 可以使用 [命令行](../reference/cli/index.md) 启动:
appium
```
这会启动Appium服务器进程,该进程加载所有已安装的Appium驱动程序,并开始等待来自客户端连接(如测试自动化脚本)的新会话请求。
由于服务器进程独立于其客户端,因此必须在尝试启动新会话之前明确启动它。
这会启动Appium服务器进程,该进程加载所有已安装的Appium驱动程序,并开始等待来自客户端连接(如测试自动化脚本)的新会话请求。由于服务器进程独立于其客户端,因此必须在尝试启动新会话之前明确启动它。
当服务器启动时,控制台日志将列出客户端可以用来连接到此服务器的所有有效URL:
@@ -42,6 +41,6 @@ appium
(... any other URLs ...)
```
一旦客户端请求新会话,Appium服务器进程将开始记录此会话的所有详细信息,直到其终止。 请记住这一点——如果您遇到Appium测试的问题,您可以随时检查服务器日志以获取更多详细信息。
一旦客户端请求新会话,Appium服务器进程将开始记录此会话的所有详细信息,直到其终止。请记住这一点——如果您遇到Appium测试的问题,您可以随时检查服务器日志以获取更多详细信息。
接下来是什么? 即使Appium已安装并运行,它也不捆绑任何驱动程序,这意味着它还不能自动化任何东西。 因此,我们将为Android设置自动化——继续[安装UiAutomator2驱动程序](./uiauto2-driver.md)。
接下来是什么?即使Appium已安装并运行,它也不捆绑任何驱动程序,这意味着它还不能自动化任何东西。因此,我们将为Android设置自动化——继续[安装UiAutomator2驱动程序](./uiauto2-driver.md)。
@@ -5,7 +5,7 @@ hide:
title: 下一步
---
现在您已成功为Android自动化设置了系统并运行了一个简单的测试,您将希望继续探索此文档。 特别是,这些是适合初学者的优秀指南和参考资料: 特别是,这些是适合初学者的优秀指南和参考资料:
现在您已成功为Android自动化设置了系统并运行了一个简单的测试,您将希望继续探索此文档。 特别是,这些是适合初学者的优秀指南和参考资料:特别是,这些是适合初学者的优秀指南和参考资料:
- [生态系统](../ecosystem/index.md) 页面:浏览可用的驱动程序、客户端、插件和工具
- [管理Appium驱动程序和插件](../guides/managing-exts.md)
@@ -12,10 +12,10 @@ Appium 服务器的基本要求如下:
- 推荐使用 LTS 版本
- [`npm`](https://npmjs.com) 版本 `>=10`(`npm` 通常与 Node.js 捆绑,但可以独立升级)
Appium 本身相对轻量,没有显著的磁盘空间或 RAM 要求。 即使在资源受限的环境如 Raspberry Pi 中,只要有 Node.js 就可以运行。
Appium 本身相对轻量,没有显著的磁盘空间或 RAM 要求。即使在资源受限的环境如 Raspberry Pi 中,只要有 Node.js 就可以运行。
### 驱动程序要求
用于自动化特定平台的驱动程序可能有其他要求。 请参考该平台的 [Appium 驱动程序](../ecosystem/drivers.md) 文档以获取其他依赖项。 普遍的情况是,给定平台的 Appium 驱动程序需要安装该平台的开发者工具链和 SDK。
用于自动化特定平台的驱动程序可能有其他要求。请参考该平台的 [Appium 驱动程序](../ecosystem/drivers.md) 文档以获取其他依赖项。普遍的情况是,给定平台的 Appium 驱动程序需要安装该平台的开发者工具链和 SDK。
为了帮助处理驱动程序要求,每个(官方)驱动程序都附带 Appium Doctor 工具,它允许验证是否已设置所有要求。 了解如何使用此工具的更多信息,请参阅 [命令行使用文档](../reference/cli/extensions.md#doctor)。
为了帮助处理驱动程序要求,每个(官方)驱动程序都附带 Appium Doctor 工具,它允许验证是否已设置所有要求。了解如何使用此工具的更多信息,请参阅 [命令行使用文档](../reference/cli/extensions.md#doctor)。
@@ -6,9 +6,9 @@ title: 编写测试 (.NET)
---
[Appium .NET Client](https://github.com/appium/dotnet-client/) 是
官方的 Appium C# 客户端。 这个驱动程序是 Selenium C# 客户端的扩展。 它具有常规驱动程序的所有功能,但在之上添加了 Appium 特定的方法。 该驱动程序在公共 NuGet Gallery 上作为 [Appium.WebDriver](https://www.nuget.org/packages/Appium.WebDriver/) 提供。
官方的 Appium C# 客户端。这个驱动程序是 Selenium C# 客户端的扩展。它具有常规驱动程序的所有功能,但在之上添加了 Appium 特定的方法。该驱动程序在公共 NuGet Gallery 上作为 [Appium.WebDriver](https://www.nuget.org/packages/Appium.WebDriver/) 提供。
现在,我们进入目录并创建一个新的 [NUnit](https://nunit.org/) 项目。 我们还将添加对 Appium.Net 驱动程序和其他依赖项的引用。
现在,我们进入目录并创建一个新的 [NUnit](https://nunit.org/) 项目。我们还将添加对 Appium.Net 驱动程序和其他依赖项的引用。
```bash
cd dotnet-client
@@ -21,7 +21,7 @@ dotnet add package Appium.WebDriver --prerelease
dotnet add package Newtonsoft.Json --version 13.0.3
```
完成后,您的项目应该有一个占位符文件 `UnitTest1.cs`。 我们将替换代码以包含 OpenQA 命名空间、驱动程序的初始化以及实际测试。
完成后,您的项目应该有一个占位符文件 `UnitTest1.cs`。我们将替换代码以包含 OpenQA 命名空间、驱动程序的初始化以及实际测试。
```C# title="UnitTest1.cs"
using OpenQA.Selenium;
@@ -77,12 +77,12 @@ public class Tests
基本上,此代码执行以下操作:
1. 定义一组"Capabilities"(参数)发送到 Appium 服务器,以便 Appium 知道您想要自动化什么。 有些参数可以使用环境变量覆盖。
1. 定义一组"Capabilities"(参数)发送到 Appium 服务器,以便 Appium 知道您想要自动化什么。有些参数可以使用环境变量覆盖。
2. 在内置的 Android 设置应用上启动 Appium 会话。
3. 查找"Apps"列表项并点击它。
4. 结束 Appium 会话。
就是这样! 让我们试试。 在运行测试之前,请确保在另一个终端会话中运行 Appium 服务器,否则您会收到无法连接的错误。 然后,您可以执行脚本:
就是这样!让我们试试。在运行测试之前,请确保在另一个终端会话中运行 Appium 服务器,否则您会收到无法连接的错误。然后,您可以执行脚本:
```bash
dotnet test
@@ -96,4 +96,4 @@ dotnet test
如果一切顺利,您将看到设置应用打开并在模拟器中导航到"Apps"视图,然后应用再次关闭。
恭喜,您已经开始了 Appium 之旅! 继续阅读一些 [后续步骤](./next-steps.md) 以进行探索。
恭喜,您已经开始了 Appium 之旅!继续阅读一些 [后续步骤](./next-steps.md) 以进行探索。
@@ -5,14 +5,12 @@ hide:
title: 编写测试 (Java)
---
Appium 团队维护了一个官方的 [客户端](https://github.com/appium/java-client) 用于 Java 编程语言。
它建立在 [Selenium](https://github.com/SeleniumHQ/selenium) 之上。
您也可以在 Kotlin 项目中使用此客户端。
Appium 团队维护了一个官方的 [客户端](https://github.com/appium/java-client) 用于 Java 编程语言。它建立在 [Selenium](https://github.com/SeleniumHQ/selenium) 之上。您也可以在 Kotlin 项目中使用此客户端。
按照 [将 Appium Java 客户端添加到您的测试框架](https://github.com/appium/java-client#add-appium-java-client-to-your-test-framework) 教程将库连接到您的测试框架源代码。
Appium Java 客户端具有专用类来支持大多数官方 Appium 驱动程序。 对于其他驱动程序,您可以简单地使用 [AppiumDriver](https://github.com/appium/java-client/blob/master/src/main/java/io/appium/java_client/AppiumDriver.java) 类
或从中构建自定义派生类。 查看 [驱动程序支持](https://github.com/appium/java-client#drivers-support) 文章以了解当前驱动程序类实现的更多信息。
Appium Java 客户端具有专用类来支持大多数官方 Appium 驱动程序。对于其他驱动程序,您可以简单地使用 [AppiumDriver](https://github.com/appium/java-client/blob/master/src/main/java/io/appium/java_client/AppiumDriver.java) 类
或从中构建自定义派生类。查看 [驱动程序支持](https://github.com/appium/java-client#drivers-support) 文章以了解当前驱动程序类实现的更多信息。
按照 [使用示例](https://github.com/appium/java-client#usage-examples) 文章以了解
如何从您的测试框架调用 Java 客户端功能。
@@ -5,7 +5,7 @@ hide:
title: 编写测试 (JS)
---
要在 JavaScript (Node.js) 中编写 Appium 测试,我们需要选择一个与 Appium 兼容的客户端库。 维护最好的库,也是 Appium 团队推荐使用的,是 [WebdriverIO](https://webdriver.io),所以让我们使用它。 由于我们已经安装了 Appium,我们知道我们的 Node 和 NPM 已经满足要求。 所以在您的计算机上某个地方创建一个新的项目目录,然后在其中初始化一个新的 Node.js 项目:
要在 JavaScript (Node.js) 中编写 Appium 测试,我们需要选择一个与 Appium 兼容的客户端库。维护最好的库,也是 Appium 团队推荐使用的,是 [WebdriverIO](https://webdriver.io),所以让我们使用它。由于我们已经安装了 Appium,我们知道我们的 Node 和 NPM 已经满足要求。所以在您的计算机上某个地方创建一个新的项目目录,然后在其中初始化一个新的 Node.js 项目:
```bash
npm init
@@ -25,7 +25,7 @@ npm i --save-dev webdriverio
--8<-- "./sample-code/quickstarts/js/package.json"
```
现在是编写测试本身的时候了。 创建一个名为 `test.js` 的新文件,内容如下:
现在是编写测试本身的时候了。创建一个名为 `test.js` 的新文件,内容如下:
```js title="test.js"
--8<-- "./sample-code/quickstarts/js/test.js"
@@ -51,7 +51,7 @@ npm i --save-dev webdriverio
4. 暂停片刻纯粹为了视觉效果。
5. 结束 Appium 会话。
就是这样! 让我们试试。 在运行测试之前,请确保在另一个终端会话中运行 Appium 服务器,否则您会收到无法连接的错误。 然后,您可以执行脚本:
就是这样!让我们试试。在运行测试之前,请确保在另一个终端会话中运行 Appium 服务器,否则您会收到无法连接的错误。然后,您可以执行脚本:
```bash
node test.js
@@ -59,4 +59,4 @@ node test.js
如果一切顺利,您将看到设置应用打开并导航到"Apps"视图,然后应用再次关闭。
恭喜,您已经开始了 Appium 之旅! 继续阅读一些 [后续步骤](./next-steps.md) 以进行探索。
恭喜,您已经开始了 Appium 之旅!继续阅读一些 [后续步骤](./next-steps.md) 以进行探索。
@@ -6,8 +6,7 @@ title: 编写测试 (Python)
---
[Appium Python Client](https://github.com/appium/python-client) 是
官方的 Python Appium 客户端,可通过 pypi 以 [Appium-Python-Client](https://pypi.org/project/Appium-Python-Client/) 包名获取。
它继承自 [Selenium Python Binding](https://pypi.org/project/selenium/),
官方的 Python Appium 客户端,可通过 pypi 以 [Appium-Python-Client](https://pypi.org/project/Appium-Python-Client/) 包名获取。它继承自 [Selenium Python Binding](https://pypi.org/project/selenium/),
因此安装 Appium Python Client 包括 selenium 绑定。
```bash
@@ -15,8 +14,7 @@ pip install Appium-Python-Client
```
此示例使用 Python 的内置 `unittest` 模块,您也可以使用任何您想要的 Python 测试框架。
Appium Python 客户端自动添加 `appium:` 前缀。
您通常不需要担心前缀。
Appium Python 客户端自动添加 `appium:` 前缀。您通常不需要担心前缀。
```python title="test.py"
--8<-- "./sample-code/quickstarts/py/test.py"
@@ -46,7 +44,7 @@ Appium Python 客户端自动添加 `appium:` 前缀。
4. 暂停片刻纯粹为了视觉效果。
5. 结束 Appium 会话。
就是这样! 让我们试试。 在运行测试之前,请确保在另一个终端会话中运行 Appium 服务器,否则您会收到无法连接的错误。 然后,您可以执行脚本:
就是这样!让我们试试。在运行测试之前,请确保在另一个终端会话中运行 Appium 服务器,否则您会收到无法连接的错误。然后,您可以执行脚本:
```bash
python test.py
@@ -54,4 +52,4 @@ python test.py
如果一切顺利,您将看到设置应用打开并导航到"Apps"视图,然后应用再次关闭。
恭喜,您已经开始了 Appium 之旅! 继续阅读一些 [后续步骤](./next-steps.md) 以进行探索。
恭喜,您已经开始了 Appium 之旅!继续阅读一些 [后续步骤](./next-steps.md) 以进行探索。
@@ -5,7 +5,7 @@ hide:
title: 编写测试 (Ruby)
---
[AppiumLib](https://github.com/appium/ruby_lib) 和 [AppiumLibCore](https://github.com/appium/ruby_lib_core)(**推荐**)是 Ruby 中的官方 Appium 客户端库,可通过 gem 以 [appium_lib](https://rubygems.org/gems/appium_lib) 和 [appium_lib_core](https://rubygems.org/gems/appium_lib_core) 包名获取。 appium_lib_core 继承自 Selenium Ruby Binding,而 appium_lib 继承自 appium_lib_core,因此安装这些库包括 selenium 绑定。 如果您需要一个不太复杂的客户端解决方案,我们推荐 `appium_lib_core`。 `appium_lib` 有一些核心没有的有用方法,但代价是更大的复杂性和可能在最新环境中不工作的历史方法。
[AppiumLib](https://github.com/appium/ruby_lib) 和 [AppiumLibCore](https://github.com/appium/ruby_lib_core)(**推荐**)是 Ruby 中的官方 Appium 客户端库,可通过 gem 以 [appium_lib](https://rubygems.org/gems/appium_lib) 和 [appium_lib_core](https://rubygems.org/gems/appium_lib_core) 包名获取。 appium_lib_core 继承自 Selenium Ruby Binding,而 appium_lib 继承自 appium_lib_core,因此安装这些库包括 selenium 绑定。如果您需要一个不太复杂的客户端解决方案,我们推荐 `appium_lib_core`。 `appium_lib` 有一些核心没有的有用方法,但代价是更大的复杂性和可能在最新环境中不工作的历史方法。
作为第一步,让我们初始化一个 Gemfile 来管理依赖项:
@@ -34,11 +34,10 @@ bundle add test-unit
```
`appium_lib_core` 是作为 Appium 客户端的主要部分。
`appium_lib` 有各种辅助方法,但驱动程序实例通常被设计为全局变量使用。 它可能导致处理实例的问题。
`appium_lib` 有各种辅助方法,但驱动程序实例通常被设计为全局变量使用。它可能导致处理实例的问题。
`appium_lib_core` 没有这样的全局变量。
此示例使用 `appium_lib_core` 和 `test-unit` gem 模块。
在 `appium_lib` 中的测试代码应该类似。
此示例使用 `appium_lib_core` 和 `test-unit` gem 模块。在 `appium_lib` 中的测试代码应该类似。
```ruby title="test.rb"
--8<-- "./sample-code/quickstarts/rb/test.rb"
@@ -68,7 +67,7 @@ bundle add test-unit
4. 暂停片刻纯粹为了视觉效果。
5. 结束 Appium 会话。
就是这样! 让我们试试。 在运行测试之前,请确保在另一个终端会话中运行 Appium 服务器,否则您会收到无法连接的错误。 然后,您可以执行脚本:
就是这样!让我们试试。在运行测试之前,请确保在另一个终端会话中运行 Appium 服务器,否则您会收到无法连接的错误。然后,您可以执行脚本:
```bash
# 如果您的环境尚未运行安装命令,请先运行 "bundle install"。
@@ -77,4 +76,4 @@ bundle exec ruby test.rb
如果一切顺利,您将看到设置应用打开并导航到"Apps"视图,然后应用再次关闭。
恭喜,您已经开始了 Appium 之旅! 继续阅读一些 [后续步骤](./next-steps.md) 以进行探索。
恭喜,您已经开始了 Appium 之旅!继续阅读一些 [后续步骤](./next-steps.md) 以进行探索。
@@ -10,7 +10,7 @@ title: 安装 UiAutomator2 驱动程序
对于这个快速入门指南,我们将在 Android 平台上自动化一个应用,因为通过 Appium 进行 Android 自动化的系统要求与 Appium 本身相同(而 iOS 驱动程序,则要求您使用 macOS)。
```
我们要使用的驱动程序称为 [UiAutomator2 驱动程序](https://github.com/appium/appium-uiautomator2-driver)。 请访问该驱动程序的文档并将其加入书签,因为它将成为未来宝贵的参考。
我们要使用的驱动程序称为 [UiAutomator2 驱动程序](https://github.com/appium/appium-uiautomator2-driver)。请访问该驱动程序的文档并将其加入书签,因为它将成为未来宝贵的参考。
## 设置 Android 自动化要求
@@ -18,29 +18,26 @@ title: 安装 UiAutomator2 驱动程序
### Android SDK
- 设置 Android SDK 的最简单方法是下载 [Android Studio](https://developer.android.com/studio)。
我们需要使用其 SDK 管理器(_设置 -> 语言和框架 -> Android SDK_)下载以下项目:
- 设置 Android SDK 的最简单方法是下载 [Android Studio](https://developer.android.com/studio)。我们需要使用其 SDK 管理器(_设置 -> 语言和框架 -> Android SDK_)下载以下项目:
- Android SDK 平台(选择我们想要自动化的任何 Android 平台,例如 API 级别 30)
- Android SDK 平台工具
- 如果您愿意,您也可以在没有 Android Studio 的情况下下载这些项目:
- Android SDK 平台可以使用包含在 [Android 命令行工具](https://developer.android.com/studio#command-line-tools-only) 中的 `sdkmanager` 下载
- [Android SDK 平台工具](https://developer.android.com/tools/releases/platform-tools)
- 设置 `ANDROID_HOME` 环境变量指向安装 Android SDK 的目录。 您通常可以在 Android Studio SDK 管理器中找到此目录的路径。 它将包含 `platform-tools` 和其他目录。
- 设置 `ANDROID_HOME` 环境变量指向安装 Android SDK 的目录。您通常可以在 Android Studio SDK 管理器中找到此目录的路径。它将包含 `platform-tools` 和其他目录。
### Java JDK
- 安装 Java JDK(对于最新的 Android API 级别,需要 JDK 9,否则请用 JDK 8)。 您可以从 [Oracle](https://jdk.java.net/) 或 [Adoptium](https://adoptium.net/en-GB/temurin/releases/) 下载此软件。
确保您获得 JDK 而不是 JRE。
- 设置 `JAVA_HOME` 环境变量指向 JDK 主目录。 它将包含 `bin`、`include` 和其他目录。
- 安装 Java JDK(对于最新的 Android API 级别,需要 JDK 9,否则请用 JDK 8)。您可以从 [Oracle](https://jdk.java.net/) 或 [Adoptium](https://adoptium.net/en-GB/temurin/releases/) 下载此软件。确保您获得 JDK 而不是 JRE。
- 设置 `JAVA_HOME` 环境变量指向 JDK 主目录。它将包含 `bin`、`include` 和其他目录。
### 准备设备
- 如果使用模拟器,使用 Android Studio 创建和启动 Android 虚拟设备 (AVD)。
您可能需要下载要创建的模拟器的 API 级别的系统映像。 使用 Android Studio 中的 AVD 创建向导通常是完成所有这些的最简单方法。
- 如果使用模拟器,使用 Android Studio 创建和启动 Android 虚拟设备 (AVD)。您可能需要下载要创建的模拟器的 API 级别的系统映像。使用 Android Studio 中的 AVD 创建向导通常是完成所有这些的最简单方法。
- 如果使用真实设备,您应该为其设置[开发者模式并启用 USB 调试](https://developer.android.com/studio/debug/dev-options)。
- 连接模拟器或设备后,您可以运行 `adb devices`(adb位于 `$ANDROID_HOME/platform-tools/adb`)来验证您的设备是否显示为已连接。
一旦您的设备在 `adb` 中显示为已连接,并且您验证了环境变量设置正确,您应该准备好了! 如果您在这些步骤中的任何一个遇到问题,请参考驱动程序文档,或根据需要参考各种 Android 或 Java 文档站点。
一旦您的设备在 `adb` 中显示为已连接,并且您验证了环境变量设置正确,您应该准备好了!如果您在这些步骤中的任何一个遇到问题,请参考驱动程序文档,或根据需要参考各种 Android 或 Java 文档站点。
恭喜:无论您是否打算这样做,您现在已经在系统上设置了 Android 开发者工具链,所以如果您愿意,可以开始制作 Android 应用!
@@ -48,11 +45,9 @@ title: 安装 UiAutomator2 驱动程序
### 标准安装
像所有 Appium 驱动程序一样,UiAutomator 2 驱动程序通过 [Appium 扩展 CLI](../reference/cli/extensions.md) 安装。
由于 UiAutomator 2 由核心 Appium 团队维护,它有一个“官方”驱动程序名称(`uiautomator2`),这使得安装更简单。
像所有 Appium 驱动程序一样,UiAutomator 2 驱动程序通过 [Appium 扩展 CLI](../reference/cli/extensions.md) 安装。由于 UiAutomator 2 由核心 Appium 团队维护,它有一个“官方”驱动程序名称(`uiautomator2`),这使得安装更简单。
在安装之前,确保您的 Appium 服务&#x5668;_&#x672A;_&#x8FD0;行(如果正在运行,请用 _Ctrl-C_ 退出)。
然后运行以下命令:
在安装之前,确保您的 Appium 服务&#x5668;_&#x672A;_&#x8FD0;行(如果正在运行,请用 _Ctrl-C_ 退出)。然后运行以下命令:
```bash
appium driver install uiautomator2
@@ -78,7 +73,7 @@ Driver uiautomator2@2.0.5 successfully installed
### 批量安装
您可能希望将 Appium 与多个驱动程序一起使用。 一种方法是为每个单独的驱动程序运行 `appium driver install <driver-name>`,但您也可以一次性安装多个驱动程序:
您可能希望将 Appium 与多个驱动程序一起使用。一种方法是为每个单独的驱动程序运行 `appium driver install <driver-name>`,但您也可以一次性安装多个驱动程序:
```
appium setup
@@ -86,8 +81,7 @@ appium setup
运行此命令将安装 Appium 的移动端特定驱动程序:UiAutomator2、[XCUITest](https://appium.github.io/appium-xcuitest-driver/)(仅在运行 macOS 时),以及 [Espresso](https://github.com/appium/appium-espresso-driver)。
您也可以使用此命令批量安装桌面应用程序或桌面浏览器的驱动程序。
有关更多详细信息,请参考[设置命令文档](../reference/cli/setup.md)。
您也可以使用此命令批量安装桌面应用程序或桌面浏览器的驱动程序。有关更多详细信息,请参考[设置命令文档](../reference/cli/setup.md)。
### 验证安装
@@ -97,7 +91,7 @@ UiAutomator2 驱动程序,像所有官方 Appium 驱动程序一样,带有 A
appium driver doctor uiautomator2
```
本指南专注于基本要求,因此 Appium Doctor 可能建议一个或多个可选修复。 但如果您看到 `0 required fixes needed`,那意味着一切都设置好了!
本指南专注于基本要求,因此 Appium Doctor 可能建议一个或多个可选修复。但如果您看到 `0 required fixes needed`,那意味着一切都设置好了!
现在,再次启动 Appium 服务器(运行 `appium`),您应该看到新安装的驱动程序列为可用:
@@ -106,7 +100,7 @@ appium driver doctor uiautomator2
[Appium] - uiautomator2@2.0.5 (automationName 'UiAutomator2')
```
Android 设置完成并安装了 UiAutomator2 驱动程序,准备编写第一个测试吧! 现在选择您喜欢的语言并试试:
Android 设置完成并安装了 UiAutomator2 驱动程序,准备编写第一个测试吧!现在选择您喜欢的语言并试试:
<div class="grid cards" markdown>