

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# AWS Device Farm 中的 Appium 測試
<a name="appium-endpoint"></a>

在遠端存取工作階段期間，您可以從本機環境執行 Appium 測試，並使用受管 Appium 端點鎖定工作階段的裝置。透過 Appium 端點，您可以利用快速回饋和快速迭代來開發、測試和執行 Appium 程式碼。此**用戶端測試**方法可讓您靈活地從您選擇的任何 Appium 用戶端環境連線至 Device Farm 裝置。

為了補充用戶端測試，Device Farm 也支援在由 服務管理的基礎設施上執行測試，稱為**伺服器端**執行。在此方法中，您可以將應用程式和測試上傳至服務，然後使用服務受管測試[主機在多個裝置上平行執行測試](custom-test-environments-hosts.md)。此方法非常適合在許多裝置上獨立進行測試，以及根據 CI/CD 管道的內容進行測試。

若要進一步了解伺服器端執行，請參閱 [AWS Device Farm 中的測試架構和內建測試](test-types.md)。

**Topics**
+ [什麼是 Appium 端點？](#appium-endpoint-what-is)
+ [Appium 測試入門](appium-endpoint-getting-started.md)
+ [使用 Appium 與裝置互動](appium-endpoint-interaction.md)
+ [檢閱您的 Appium 伺服器日誌](appium-endpoint-server-logs.md)
+ [支援的 Appium 功能和命令](appium-endpoint-supported-caps-and-commands.md)

## 什麼是 Appium 端點？
<a name="appium-endpoint-what-is"></a>

[Appium](https://appium.io/) 是熱門的開放原始碼軟體測試架構，可在不同的裝置上測試 iOS 和 Android 的原生、混合和行動 Web 應用程式，包括行動電話和平板電腦。它可讓開發人員和 QA （品質保證） 工程師撰寫指令碼，以遠端控制裝置、模擬使用者互動，並確認測試中的應用程式如預期般運作。Appium 從最終使用者的角度與應用程式互動，讓測試人員能夠開發測試，模擬真實使用者將如何使用應用程式進行測試。

Appium 是以用戶端-伺服器模型為基礎，其中本機用戶端會請求 （本機或遠端） Appium 伺服器代其命令裝置。Appium 伺服器會管理與裝置通訊的驅動程式，例如適用於 Android 的 [UIAutomator2 驅動程式](https://github.com/appium/appium-uiautomator2-driver/)或適用於 iOS 的 [XCUITest 驅動程式](https://appium.github.io/appium-xcuitest-driver/9.10/)。所有命令都遵循 [W3C WebDriver](https://www.w3.org/TR/webdriver2/) 標準，了解如何控制裝置。

Device Farm 的 Appium 端點會在遠端存取工作階段中公開裝置的 Appium 伺服器 URL。Appium 端點 URL 將專屬於該工作階段中的該裝置，並在工作階段期間保持有效，可讓您在相同裝置上迭代，而無需額外的設定時間。如需遠端存取的詳細資訊，請參閱 [AWS Device Farm 中的遠端存取](remote-access.md)。

# Appium 測試入門
<a name="appium-endpoint-getting-started"></a>

對於大多數 Appium 使用者，使用 Device Farm for Appium 測試只需要對現有測試組態進行次要變更。

在高階，使用 Device Farm 進行用戶端 Appium 測試有三個步驟：

1. 首先，您需要[建立遠端存取工作階段](how-to-create-session.md)來測試 Device Farm 裝置。您可以在遠端存取請求中包含您的應用程式，或在工作階段開始後安裝應用程式。

1. 工作階段執行後，您可以[複製 Appium 端點 URL](appium-endpoint-interaction.md)，並透過獨立工具 （例如 [Appium Inspector](https://github.com/appium/appium-inspector)) 或從 IDE 中的 Appium 測試程式碼使用它。URL 在遠端存取工作階段期間有效。

1. 最後，一旦您的 Appium 測試開始，您就可以[在測試執行期間與裝置的影片串流一起即時檢閱 Appium 伺服器日誌](appium-endpoint-server-logs.md)。

# 使用 Appium 與裝置互動
<a name="appium-endpoint-interaction"></a>

[建立遠端存取工作階段](how-to-create-session.md)後，裝置將可用於 Appium 測試。在遠端存取工作階段的整個期間，您可以視需要在裝置上執行任意數量的 Appium 工作階段，而不會限制您使用的用戶端。例如，您可以從 IDE 使用本機 Appium 程式碼執行測試，然後切換到使用 Appium Inspector 來疑難排解您遇到的任何問題。工作階段最多可持續 [150 分鐘](limits.md#service-limits)，不過，如果超過 5 分鐘沒有活動 （透過互動式主控台或透過 Appium 端點），工作階段將會逾時。

## 使用應用程式搭配 Appium 工作階段進行測試
<a name="appium-endpoint-using-apps"></a>

Device Farm 可讓您使用應用程式作為遠端存取工作階段建立請求的一部分，或在遠端存取工作階段本身期間安裝應用程式。這些應用程式會自動安裝在待測裝置上，並插入為任何 Appium 工作階段請求的預設功能。當您建立遠端存取工作階段時，您可以選擇傳入應用程式 ARN，該應用程式 ARN 預設會用作所有後續 Appium 工作階段`appium:app`的功能，以及輔助應用程式 ARNs，其將用作`appium:otherApps`功能。

例如，如果您使用 應用程式`com.aws.devicefarm.sample`建立遠端存取工作階段，並`com.aws.devicefarm.other.sample`做為其中一個輔助應用程式，則當您前往建立 Appium 工作階段時，它將具有類似下列的功能：

```
{
    "value":
    {
        "sessionId": "abcdef123456-1234-5678-abcd-abcdef123456",
        "capabilities":
        {
            "app": "/tmp/com.aws.devicefarm.sample.apk",
            "otherApps": "[\"/tmp/com.aws.devicefarm.other.sample.apk\"]",
            ...
        }
    }
}
```

在工作階段期間，您可以安裝其他應用程式 （在主控台內或使用 [https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_InstallToRemoteAccessSession.html](https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_InstallToRemoteAccessSession.html) API)。這些將覆寫先前用作 `appium:app`功能的任何現有應用程式。如果先前使用的應用程式具有不同的套件名稱，它們將保留在裝置上，並用作`appium:otherApps`功能的一部分。

例如，如果您最初在建立遠端存取工作階段`com.aws.devicefarm.sample`時使用應用程式，然後在工作階段`com.aws.devicefarm.other.sample`期間安裝名為 的新應用程式，則您的 Appium 工作階段將具有類似下列的功能：

```
{
    "value":
    {
        "sessionId": "abcdef123456-1234-5678-abcd-abcdef123456",
        "capabilities":
        {
            "app": "/tmp/com.aws.devicefarm.other.sample.apk",
            "otherApps": "[\"/tmp/com.aws.devicefarm.sample.apk\"]",
            ...
        }
    }
}
```

如果您願意，您可以使用應用程式名稱明確指定應用程式的功能 （分別使用 Android 和 iOS 的 `appium:appPackage`或 `appium:bundleId`功能）。

如果您要測試 Web 應用程式，請指定 Appium 工作階段建立請求`browserName`的功能。`Chrome` 瀏覽器適用於所有 Android 裝置，瀏覽器`Safari`適用於所有 iOS 裝置。

Device Farm 不支援在遠端存取工作階段`appium:app`期間在 中傳遞遠端 URL 或本機檔案系統路徑。將應用程式上傳至 Device Farm，並改為將其包含在工作階段中。

**注意**  
如需在遠端存取工作階段中自動上傳應用程式的詳細資訊，請參閱[自動上傳應用程式。](api-ref.md#upload-example)

## 如何使用 Appium 端點
<a name="appium-endpoint-how-to-use"></a>

以下是從主控台 AWS CLI、 和 AWS SDKs 存取工作階段 Appium 端點的步驟。這些步驟包括如何使用各種 Appium 用戶端測試架構開始執行測試：

------
#### [ Console ]

1. 在 Web 瀏覽器中開啟遠端存取工作階段頁面：  
![\[\]](http://docs.aws.amazon.com/zh_tw/devicefarm/latest/developerguide/images/aws-device-farm-appium-endpoint.png)

1. 若要使用 Appium Inspector 執行工作階段，請執行下列動作：

   1. 按一下按鈕**設定 Appium 工作階段**

   1. 遵循頁面上的指示，了解如何使用 Appium Inspector 啟動工作階段。

1. 若要從本機 IDE 執行 Appium 測試，請執行下列動作：

   1. 按一下文字 **Appium 端點 URL** 旁的「複製」圖示

   1. 將此 URL 貼到您的本機 Appium 程式碼，只要您目前指定遠端地址或命令執行器。如需特定語言的範例，請按一下此範例視窗中您所選語言的其中一個標籤。

------
#### [ AWS CLI ]

首先，[下載並安裝最新版本，以確認您的 AWS CLI 版本](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)是up-to-date。

**重要**  
Appium 端點欄位不適用於舊版的 AWS CLI。

一旦您的工作階段啟動並執行，Appium 端點 URL 將透過 [https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_GetRemoteAccessSession.html](https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_GetRemoteAccessSession.html) API 呼叫回應`remoteDriverEndpoint`中名為 的欄位提供：

```
$ aws devicefarm get-remote-access-session \
    --arn "arn:aws:devicefarm:us-west-2:123456789876:session:abcdef123456-1234-5678-abcd-abcdef123456/abcdef123456-1234-5678-abcd-abcdef123456/00000"
```

這會顯示如下所示的輸出：

```
{
    "remoteAccessSession": {
        "arn": "arn:aws:devicefarm:us-west-2:111122223333:session:abcdef123456-1234-5678-abcd-abcdef123456/abcdef123456-1234-5678-abcd-abcdef123456/00000",
        "name": "Google Pixel 8",
        "status": "RUNNING",
        "endpoints": {
            "remoteDriverEndpoint": "https://devicefarm-interactive-global.us-west-2.api.aws/remote-endpoint/ABCD1234...",
        ...
}
```

無論您目前在何處指定遠端地址或命令執行器，都可以在本機 Appium 程式碼中使用此 URL。如需特定語言的範例，請按一下此範例視窗中您所選語言的其中一個標籤。

如需如何直接從命令列與端點互動的範例，您可以使用[命令列工具 curl ](https://curl.se/)直接呼叫 WebDriver 端點：

```
$ curl "https://devicefarm-interactive-global.us-west-2.api.aws/remote-endpoint/ABCD1234.../status"
```

這會顯示如下所示的輸出：

```
{
    "value":
    {
        "ready": true,
        "message": "The server is ready to accept new connections",
        "build":
        {
            "version": "2.5.1"
        }
    }
}
```

------
#### [ Python ]

一旦您的工作階段啟動並執行，Appium 端點 URL 將透過 [https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_GetRemoteAccessSession.html](https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_GetRemoteAccessSession.html) API 呼叫回應`remoteDriverEndpoint`中名為 的欄位提供：

```
# To get the URL
import sys
import boto3
from botocore.exceptions import ClientError

def get_appium_endpoint() -> str:
    session_arn = "arn:aws:devicefarm:us-west-2:111122223333:session:abcdef123456-1234-5678-abcd-abcdef123456/abcdef123456-1234-5678-abcd-abcdef123456/00000"
    device_farm_client = boto3.client("devicefarm", region_name="us-west-2")

    try:
        resp = device_farm_client.get_remote_access_session(arn=session_arn)
    except ClientError as exc:
        sys.exit(f"Failed to call Device Farm: {exc}")

    remote_access_session = resp.get("remoteAccessSession", {})
    endpoints = remote_access_session.get("endpoints", {})
    endpoint = endpoints.get("remoteDriverEndpoint")

    if not endpoint:
        sys.exit("Device Farm response did not include endpoints.remoteDriverEndpoint")

    return endpoint

# To use the URL
from appium import webdriver
from appium.options.android import UiAutomator2Options

opts = UiAutomator2Options()
driver = webdriver.Remote(get_appium_endpoint(), options=opts)
# ...
driver.quit()
```

------
#### [ Java ]

*注意：此範例使用適用於 Java v2 的 AWS 開發套件，並與 JDK 第 11 版及更新版本相容。*

一旦您的工作階段啟動並執行，Appium 端點 URL 將透過 [https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_GetRemoteAccessSession.html](https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_GetRemoteAccessSession.html) API 呼叫回應`remoteDriverEndpoint`中名為 的欄位提供：

```
// To get the URL
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.devicefarm.DeviceFarmClient;
import software.amazon.awssdk.services.devicefarm.model.GetRemoteAccessSessionRequest;
import software.amazon.awssdk.services.devicefarm.model.GetRemoteAccessSessionResponse;

public class AppiumEndpointBuilder {
    public static String getAppiumEndpoint() throws Exception {
        String session_arn = "arn:aws:devicefarm:us-west-2:111122223333:session:abcdef123456-1234-5678-abcd-abcdef123456/abcdef123456-1234-5678-abcd-abcdef123456/00000";

        try (DeviceFarmClient client = DeviceFarmClient.builder()
                .region(Region.US_WEST_2)
                .credentialsProvider(DefaultCredentialsProvider.create())
                .build()) {

            GetRemoteAccessSessionResponse resp = client.getRemoteAccessSession(
                    GetRemoteAccessSessionRequest.builder().arn(session_arn).build()
            );

            String endpoint = resp.remoteAccessSession().endpoints().remoteDriverEndpoint();
            if (endpoint == null || endpoint.isEmpty()) {
                throw new IllegalStateException("remoteDriverEndpoint missing from response");
            }
            return endpoint;
        }
    }
}

// To use the URL
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;

import java.net.URL;

public class ExampleTest {
    public static void main(String[] args) throws Exception {
        String endpoint = AppiumEndpointBuilder.getAppiumEndpoint();
        UiAutomator2Options options = new UiAutomator2Options();
        AndroidDriver driver = new AndroidDriver(new URL(endpoint), options);

        try {
            // ... your test ...
        } finally {
            driver.quit();
        }
    }
}
```

------
#### [ JavaScript ]

*注意：此範例使用適用於 JavaScript v3 的 AWS SDK，以及使用 Node 18\$1 的 WebdriverIO v8\$1。*

一旦您的工作階段啟動並執行，Appium 端點 URL 將透過 [https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_GetRemoteAccessSession.html](https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_GetRemoteAccessSession.html) API 呼叫回應`remoteDriverEndpoint`中名為 的欄位提供：

```
// To get the URL
import { DeviceFarmClient, GetRemoteAccessSessionCommand } from "@aws-sdk/client-device-farm";

export async function getAppiumEndpoint() {
  const sessionArn = "arn:aws:devicefarm:us-west-2:111122223333:session:abcdef123456-1234-5678-abcd-abcdef123456/abcdef123456-1234-5678-abcd-abcdef123456/00000";

  const client = new DeviceFarmClient({ region: "us-west-2" });
  const resp = await client.send(new GetRemoteAccessSessionCommand({ arn: sessionArn }));

  const endpoint = resp?.remoteAccessSession?.endpoints?.remoteDriverEndpoint;
  if (!endpoint) throw new Error("remoteDriverEndpoint missing from response");
  return endpoint;
}

// To use the URL with WebdriverIO
import { remote } from "webdriverio";

(async () => {
  const endpoint = await getAppiumEndpoint();
  const u = new URL(endpoint);

  const driver = await remote({
    protocol: u.protocol.replace(":", ""),
    hostname: u.hostname,
    port: u.port ? Number(u.port) : (u.protocol === "https:" ? 443 : 80),
    path: u.pathname + u.search,
    capabilities: {
      platformName: "Android",
      "appium:automationName": "UiAutomator2",
      // ...other caps...
    },
  });

  try {
    // ... your test ...
  } finally {
    await driver.deleteSession();
  }
})();
```

------
#### [ C\$1 ]

一旦您的工作階段啟動並執行，Appium 端點 URL 將透過 [https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_GetRemoteAccessSession.html](https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_GetRemoteAccessSession.html) API 呼叫回應`remoteDriverEndpoint`中名為 的欄位提供：

```
// To get the URL
using System;
using System.Threading.Tasks;
using Amazon;
using Amazon.DeviceFarm;
using Amazon.DeviceFarm.Model;

public static class AppiumEndpointBuilder
{
    public static async Task<string> GetAppiumEndpointAsync()
    {
        var sessionArn = "arn:aws:devicefarm:us-west-2:111122223333:session:abcdef123456-1234-5678-abcd-abcdef123456/abcdef123456-1234-5678-abcd-abcdef123456/00000";

        var config = new AmazonDeviceFarmConfig
        {
            RegionEndpoint = RegionEndpoint.USWest2
        };
        using var client = new AmazonDeviceFarmClient(config);

        var resp = await client.GetRemoteAccessSessionAsync(new GetRemoteAccessSessionRequest { Arn = sessionArn });
        var endpoint = resp?.RemoteAccessSession?.Endpoints?.RemoteDriverEndpoint;

        if (string.IsNullOrWhiteSpace(endpoint))
            throw new InvalidOperationException("RemoteDriverEndpoint missing from response");

        return endpoint;
    }
}

// To use the URL
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;

class Example
{
    static async Task Main()
    {
        var endpoint = await AppiumEndpointBuilder.GetAppiumEndpointAsync();

        var options = new AppiumOptions();
        options.PlatformName = "Android";
        options.AutomationName = "UiAutomator2";

        using var driver = new AndroidDriver(new Uri(endpoint), options);
        try
        {
            // ... your test ...
        }
        finally
        {
            driver.Quit();
        }
    }
}
```

------
#### [ Ruby ]

一旦您的工作階段啟動並執行，Appium 端點 URL 將透過 [https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_GetRemoteAccessSession.html](https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_GetRemoteAccessSession.html) API 呼叫回應`remoteDriverEndpoint`中名為 的欄位提供：

```
# To get the URL
require 'aws-sdk-devicefarm'

def get_appium_endpoint
  session_arn = "arn:aws:devicefarm:us-west-2:111122223333:session:abcdef123456-1234-5678-abcd-abcdef123456/abcdef123456-1234-5678-abcd-abcdef123456/00000"

  client = Aws::DeviceFarm::Client.new(region: 'us-west-2')
  resp = client.get_remote_access_session(arn: session_arn)
  endpoint = resp.remote_access_session.endpoints.remote_driver_endpoint
  raise "remote_driver_endpoint missing from response" if endpoint.nil? || endpoint.empty?
  endpoint
end

# To use the URL
require 'appium_lib_core'

endpoint = get_appium_endpoint
opts = {
  server_url: endpoint,
  capabilities: {
    'platformName' => 'Android',
    'appium:automationName' => 'UiAutomator2'
  }
}

driver = Appium::Core.for(opts).start_driver
begin
  # ... your test ...
ensure
  driver.quit
end
```

------

# 檢閱您的 Appium 伺服器日誌
<a name="appium-endpoint-server-logs"></a>

[啟動 Appium 工作階段](appium-endpoint-interaction.md)後，您可以在 Device Farm 主控台中即時檢視 Appium 伺服器日誌，或在遠端存取工作階段結束後下載日誌。以下是執行此作業的指示：

------
#### [ Console ]

1. 在 Device Farm 主控台中，開啟裝置的遠端存取工作階段。

1. 從本機 IDE 或 Appium Inspector 使用裝置啟動 Appium 端點工作階段

1. 然後，Appium 伺服器日誌將與裝置一起顯示在遠端存取工作階段頁面中，並在裝置下方的頁面底部提供「工作階段資訊」：  
![\[\]](http://docs.aws.amazon.com/zh_tw/devicefarm/latest/developerguide/images/aws-device-farm-appium-endpoint-logs.gif)

------
#### [ AWS CLI ]

*注意：此範例使用[命令列工具`curl`](https://curl.se/)從 Device Farm 提取日誌。*

在工作階段期間或之後，您可以使用 Device Farm 的 [https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_ListArtifacts.html](https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_ListArtifacts.html) API 下載 Appium 伺服器日誌。

```
$ aws devicefarm list-artifacts \
  --type FILE \
  --arn arn:aws:devicefarm:us-west-2:111122223333:session:12345678-1111-2222-333-456789abcdef/12345678-1111-2222-333-456789abcdef/00000
```

這會在工作階段期間顯示如下的輸出：

```
{
    "artifacts": [
        {
            "arn": "arn:aws:devicefarm:us-west-2:111122223333:artifact:12345678-1111-2222-333-456789abcdef/12345678-1111-2222-333-456789abcdef/00000",
            "name": "AppiumServerLogOutput",
            "type": "APPIUM_SERVER_LOG_OUTPUT",
            "extension": "",
            "url": "https://prod-us-west-2-results.s3.dualstack.us-west-2.amazonaws.com/111122223333/12345678..."
        }
    ]
}
```

工作階段完成後，還有以下項目：

```
{
    "artifacts": [
        {
            "arn": "arn:aws:devicefarm:us-west-2:111122223333:artifact:12345678-1111-2222-333-456789abcdef/12345678-1111-2222-333-456789abcdef/00000",
            "name": "Appium Server Output",
            "type": "APPIUM_SERVER_OUTPUT",
            "extension": "log",
            "url": "https://prod-us-west-2-results.s3.dualstack.us-west-2.amazonaws.com/111122223333/12345678..."
        }
    ]
}
```

```
$ curl "https://prod-us-west-2-results.s3.dualstack.us-west-2.amazonaws.com/111122223333/12345678..."
```

這會顯示如下所示的輸出：

```
info Appium Welcome to Appium v2.5.4
info Appium Non-default server args:
info Appium { address: '127.0.0.1',
info Appium   allowInsecure:
info Appium    [ 'execute_driver_script',
info Appium      'session_discovery',
info Appium      'perf_record',
info Appium      'adb_shell',
info Appium      'chromedriver_autodownload',
info Appium      'get_server_logs' ],
info Appium   keepAliveTimeout: 0,
info Appium   logNoColors: true,
info Appium   logTimestamp: true,
info Appium   longStacktrace: true,
info Appium   sessionOverride: true,
info Appium   strictCaps: true,
info Appium   useDrivers: [ 'uiautomator' ] }
```

------
#### [ Python ]

*注意：此範例使用第三方`requests`套件來下載日誌，以及適用於 Python 的 AWS SDK`boto3`。*

在工作階段期間或之後，您可以使用 Device Farm 的 [https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_ListArtifacts.html](https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_ListArtifacts.html) API 擷取 Appium 伺服器日誌 URL，然後將其下載。

```
import pathlib
import requests
import boto3

def download_appium_log():
    session_arn = "arn:aws:devicefarm:us-west-2:111122223333:session:12345678-1111-2222-333-456789abcdef/12345678-1111-2222-333-456789abcdef/00000"
    client = boto3.client("devicefarm", region_name="us-west-2")

    # 1) List artifacts for the session (FILE artifacts), handling pagination
    artifacts = []
    token = None
    while True:
        kwargs = {"arn": session_arn, "type": "FILE"}
        if token:
            kwargs["nextToken"] = token
        resp = client.list_artifacts(**kwargs)
        artifacts.extend(resp.get("artifacts", []))
        token = resp.get("nextToken")
        if not token:
            break

    if not artifacts:
        raise RuntimeError("No artifacts found in this session")

    # Filter strictly to Appium server logs
    allowed = {"APPIUM_SERVER_OUTPUT", "APPIUM_SERVER_LOG_OUTPUT"}
    filtered = [a for a in artifacts if a.get("type") in allowed]
    if not filtered:
        raise RuntimeError("No Appium server log artifacts found (expected APPIUM_SERVER_OUTPUT or APPIUM_SERVER_LOG_OUTPUT)")

    # Prefer the final 'OUTPUT' log, else the live 'LOG_OUTPUT'
    chosen = (next((a for a in filtered if a.get("type") == "APPIUM_SERVER_OUTPUT"), None)
              or next((a for a in filtered if a.get("type") == "APPIUM_SERVER_LOG_OUTPUT"), None))

    url = chosen["url"]
    ext = chosen.get("extension") or "log"
    out = pathlib.Path(f"./appium_server_log.{ext}")

    # 2) Download the artifact
    with requests.get(url, stream=True) as r:
        r.raise_for_status()
        with open(out, "wb") as fh:
            for chunk in r.iter_content(chunk_size=1024 * 1024):
                if chunk:
                    fh.write(chunk)

    print(f"Saved Appium server log to: {out.resolve()}")

download_appium_log()
```

這會顯示如下所示的輸出：

```
info Appium Welcome to Appium v2.5.4
info Appium Non-default server args:
info Appium { address: '127.0.0.1', allowInsecure: [ 'execute_driver_script', ... ], useDrivers: [ 'uiautomator' ] }
```

------
#### [ Java ]

*注意：此範例使用適用於 Java v2 的 AWS SDK 和 `HttpClient` 來下載日誌，並與 JDK 第 11 版及更高版本相容。*

在工作階段期間或之後，您可以使用 Device Farm 的 [https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_ListArtifacts.html](https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_ListArtifacts.html) API 擷取 Appium 伺服器日誌 URL，然後將其下載。

```
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.devicefarm.DeviceFarmClient;
import software.amazon.awssdk.services.devicefarm.model.Artifact;
import software.amazon.awssdk.services.devicefarm.model.ArtifactCategory;
import software.amazon.awssdk.services.devicefarm.model.ListArtifactsRequest;
import software.amazon.awssdk.services.devicefarm.model.ListArtifactsResponse;

public class AppiumLogDownloader {

    public static void main(String[] args) throws Exception {
        String sessionArn = "arn:aws:devicefarm:us-west-2:111122223333:session:12345678-1111-2222-333-456789abcdef/12345678-1111-2222-333-456789abcdef/00000";

        try (DeviceFarmClient client = DeviceFarmClient.builder()
                .region(Region.US_WEST_2)
                .build()) {

            // 1) List artifacts for the session (FILE artifacts) with pagination
            List<Artifact> all = new ArrayList<>();
            String token = null;
            do {
                ListArtifactsRequest.Builder b = ListArtifactsRequest.builder()
                        .arn(sessionArn)
                        .type(ArtifactCategory.FILE);
                if (token != null) b.nextToken(token);
                ListArtifactsResponse page = client.listArtifacts(b.build());
                all.addAll(page.artifacts());
                token = page.nextToken();
            } while (token != null && !token.isBlank());

            // Filter strictly to Appium logs
            List<Artifact> filtered = all.stream()
                    .filter(a -> {
                        String t = a.typeAsString();
                        return "APPIUM_SERVER_OUTPUT".equals(t) || "APPIUM_SERVER_LOG_OUTPUT".equals(t);
                    })
                    .toList();

            if (filtered.isEmpty()) {
                throw new RuntimeException("No Appium server log artifacts found (expected APPIUM_SERVER_OUTPUT or APPIUM_SERVER_LOG_OUTPUT).");
            }

            // Prefer OUTPUT; else LOG_OUTPUT
            Artifact chosen = filtered.stream()
                    .filter(a -> "APPIUM_SERVER_OUTPUT".equals(a.typeAsString()))
                    .findFirst()
                    .orElseGet(() -> filtered.stream()
                            .filter(a -> "APPIUM_SERVER_LOG_OUTPUT".equals(a.typeAsString()))
                            .findFirst()
                            .get());

            String url = chosen.url();
            String ext = (chosen.extension() == null || chosen.extension().isBlank()) ? "log" : chosen.extension();
            Path out = Path.of("appium_server_log." + ext);

            // 2) Download the artifact with HttpClient
            HttpClient http = HttpClient.newBuilder()
                    .connectTimeout(Duration.ofSeconds(10))
                    .build();

            HttpRequest get = HttpRequest.newBuilder(URI.create(url))
                    .timeout(Duration.ofMinutes(5))
                    .GET()
                    .build();

            HttpResponse<Path> resp = http.send(get, HttpResponse.BodyHandlers.ofFile(out));
            if (resp.statusCode() / 100 != 2) {
                throw new IOException("Failed to download log, HTTP " + resp.statusCode());
            }
            System.out.println("Saved Appium server log to: " + out.toAbsolutePath());
        }
    }
}
```

這會顯示如下所示的輸出：

```
info Appium Welcome to Appium v2.5.4
info Appium Non-default server args:
info Appium { address: '127.0.0.1', ..., useDrivers: [ 'uiautomator' ] }
```

------
#### [ JavaScript ]

*注意：此範例使用適用於 JavaScript 的 AWS SDK (v3) 和 Node 18\$1 `fetch`來下載日誌。*

在工作階段期間或之後，您可以使用 Device Farm 的 [https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_ListArtifacts.html](https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_ListArtifacts.html) API 來擷取 Appium 伺服器日誌 URL，然後將其下載。

```
import { DeviceFarmClient, ListArtifactsCommand } from "@aws-sdk/client-device-farm";
import { createWriteStream } from "fs";
import { pipeline } from "stream";
import { promisify } from "util";

const pipe = promisify(pipeline);
const client = new DeviceFarmClient({ region: "us-west-2" });

const sessionArn = "arn:aws:devicefarm:us-west-2:111122223333:session:12345678-1111-2222-333-456789abcdef/12345678-1111-2222-333-456789abcdef/00000";

// 1) List artifacts for the session (FILE artifacts), handling pagination
const artifacts = [];
let nextToken;
do {
  const page = await client.send(new ListArtifactsCommand({
    arn: sessionArn,
    type: "FILE",
    nextToken
  }));
  artifacts.push(...(page.artifacts ?? []));
  nextToken = page.nextToken;
} while (nextToken);

if (!artifacts.length) throw new Error("No artifacts found");

// Strict filter to Appium logs
const filtered = (artifacts ?? []).filter(a =>
  a.type === "APPIUM_SERVER_OUTPUT" || a.type === "APPIUM_SERVER_LOG_OUTPUT"
);
if (!filtered.length) {
  throw new Error("No Appium server log artifacts found (expected APPIUM_SERVER_OUTPUT or APPIUM_SERVER_LOG_OUTPUT).");
}

// Prefer OUTPUT; else LOG_OUTPUT
const chosen =
  filtered.find(a => a.type === "APPIUM_SERVER_OUTPUT") ??
  filtered.find(a => a.type === "APPIUM_SERVER_LOG_OUTPUT");

const url = chosen.url;
const ext = chosen.extension || "log";
const outPath = `./appium_server_log.${ext}`;

// 2) Download the artifact
const resp = await fetch(url);
if (!resp.ok) {
  throw new Error(`Failed to download log: ${resp.status} ${await resp.text().catch(()=>"")}`);
}
await pipe(resp.body, createWriteStream(outPath));
console.log("Saved Appium server log to:", outPath);
```

這會顯示如下所示的輸出：

```
info Appium Welcome to Appium v2.5.4
info Appium Non-default server args:
info Appium { address: '127.0.0.1', allowInsecure: [ 'execute_driver_script', ... ], useDrivers: [ 'uiautomator' ] }
```

------
#### [ C\$1 ]

*注意：此範例使用適用於 .NET 的 AWS SDK 和 `HttpClient`下載日誌。*

在工作階段期間或之後，您可以使用 Device Farm 的 [https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_ListArtifacts.html](https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_ListArtifacts.html) API 擷取 Appium 伺服器日誌 URL，然後將其下載。

```
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Linq;
using Amazon;
using Amazon.DeviceFarm;
using Amazon.DeviceFarm.Model;

class AppiumLogDownloader
{
    static async Task Main()
    {
        var sessionArn = "arn:aws:devicefarm:us-west-2:111122223333:session:12345678-1111-2222-333-456789abcdef/12345678-1111-2222-333-456789abcdef/00000";

        using var client = new AmazonDeviceFarmClient(RegionEndpoint.USWest2);

        // 1) List artifacts for the session (FILE artifacts), handling pagination
        var all = new List<Artifact>();
        string? token = null;
        do
        {
            var page = await client.ListArtifactsAsync(new ListArtifactsRequest
            {
                Arn = sessionArn,
                Type = ArtifactCategory.FILE,
                NextToken = token
            });
            if (page.Artifacts != null) all.AddRange(page.Artifacts);
            token = page.NextToken;
        } while (!string.IsNullOrEmpty(token));

        if (all.Count == 0)
            throw new Exception("No artifacts found");

        // Strict filter to Appium logs
        var filtered = all.Where(a =>
            a.Type == "APPIUM_SERVER_OUTPUT" || a.Type == "APPIUM_SERVER_LOG_OUTPUT").ToList();

        if (filtered.Count == 0)
            throw new Exception("No Appium server log artifacts found (expected APPIUM_SERVER_OUTPUT or APPIUM_SERVER_LOG_OUTPUT).");

        // Prefer OUTPUT; else LOG_OUTPUT
        var chosen = filtered.FirstOrDefault(a => a.Type == "APPIUM_SERVER_OUTPUT")
                    ?? filtered.First(a => a.Type == "APPIUM_SERVER_LOG_OUTPUT");
        
        var url = chosen.Url;
        var ext = string.IsNullOrWhiteSpace(chosen.Extension) ? "log" : chosen.Extension;
        var outPath = $"./appium_server_log.{ext}";

        // 2) Download the artifact
        using var http = new HttpClient();
        using var resp = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
        resp.EnsureSuccessStatusCode();
        await using (var fs = File.Create(outPath))
        {
            await resp.Content.CopyToAsync(fs);
        }
        Console.WriteLine($"Saved Appium server log to: {Path.GetFullPath(outPath)}");
    }
}
```

這會顯示如下所示的輸出：

```
info Appium Welcome to Appium v2.5.4
info Appium Non-default server args:
info Appium { address: '127.0.0.1', ..., useDrivers: [ 'uiautomator' ] }
```

------
#### [ Ruby ]

*注意：此範例使用適用於 Ruby 的 AWS SDK 和 `Net::HTTP`下載日誌。*

在工作階段期間或之後，您可以使用 Device Farm 的 [https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_ListArtifacts.html](https://docs.aws.amazon.com/devicefarm/latest/APIReference/API_ListArtifacts.html) API 來擷取 Appium 伺服器日誌 URL，然後將其下載。

```
require "aws-sdk-devicefarm"
require "net/http"
require "uri"

client = Aws::DeviceFarm::Client.new(region: "us-west-2")
session_arn = "arn:aws:devicefarm:us-west-2:111122223333:session:12345678-1111-2222-333-456789abcdef/12345678-1111-2222-333-456789abcdef/00000"

# 1) List artifacts for the session (FILE artifacts), handling pagination
artifacts = []
token = nil
loop do
  page = client.list_artifacts(arn: session_arn, type: "FILE", next_token: token)
  artifacts.concat(page.artifacts || [])
  token = page.next_token
  break if token.nil? || token.empty?
end

raise "No artifacts found" if artifacts.empty?

# Strict filter to Appium logs
filtered = (artifacts || []).select { |a| ["APPIUM_SERVER_OUTPUT", "APPIUM_SERVER_LOG_OUTPUT"].include?(a.type) }
raise "No Appium server log artifacts found (expected APPIUM_SERVER_OUTPUT or APPIUM_SERVER_LOG_OUTPUT)." if filtered.empty?

# Prefer OUTPUT; else LOG_OUTPUT
chosen = filtered.find { |a| a.type == "APPIUM_SERVER_OUTPUT" } ||
         filtered.find { |a| a.type == "APPIUM_SERVER_LOG_OUTPUT" }

url = chosen.url
ext = (chosen.extension && !chosen.extension.empty?) ? chosen.extension : "log"
out_path = "./appium_server_log.#{ext}"

# 2) Download the artifact
uri = URI.parse(url)
Net::HTTP.start(uri.host, uri.port, use_ssl: (uri.scheme == "https")) do |http|
  req = Net::HTTP::Get.new(uri)
  http.request(req) do |resp|
    raise "Failed GET: #{resp.code} #{resp.body}" unless resp.code.to_i / 100 == 2
    File.open(out_path, "wb") { |f| resp.read_body { |chunk| f.write(chunk) } }
  end
end
puts "Saved Appium server log to: #{File.expand_path(out_path)}"
```

這會顯示如下所示的輸出：

```
info Appium Welcome to Appium v2.5.4
info Appium Non-default server args:
info Appium { address: '127.0.0.1', allowInsecure: [ 'execute_driver_script', ... ], useDrivers: [ 'uiautomator' ] }
```

------

# 支援的 Appium 功能和命令
<a name="appium-endpoint-supported-caps-and-commands"></a>

Device Farm 的 Appium 端點支援您在本機裝置上使用的大多數相同命令和所需功能，但有少數例外。下列清單顯示目前不支援的功能和命令。如果您的測試因為功能受限而無法如預期執行，請開啟支援案例以取得其他指導。

## 支援的功能
<a name="appium-endpoint-unsupported-capabilities"></a>

在 Device Farm 上建立 Appium 工作階段時，我們建議您擁有一組不同的功能，排除本機裝置特有的任何功能。在 Device Farm 上，如果設定某些不支援的功能，工作階段建立可能會失敗。這包括裝置特定的功能，例如 `udid`和 `platformVersion`。此外，不支援與 Android 上的 ChromeDriver 和 iOS 上的 WebDriverAgent 相關的特定功能，以及僅支援模擬器和模擬器的功能。

## 支援的命令
<a name="appium-endpoint-unsupported-commands"></a>

在實際 Android 和 iOS 裝置上正確執行的大多數 Appium 命令都會在 Device Farm 上如預期執行，但有下列排除項目：

### Appium 裝置命令 (`/appium/device`)
<a name="appium-endpoint-unsupported-device-commands"></a>
+ `install_app`
+ `finger_print`
+ `send_sms`
+ `gsm_call`
+ `gsm_signal`
+ `gsm_voice`
+ `power_ac`
+ `power_capacity`
+ `network_speed`
+ `shake`

### Appium 執行方法和指令碼 (`/execute`)
<a name="appium-endpoint-unsupported-execute-methods"></a>
+ `installApp`
+ `execEmuConsoleCommand`
+ `fingerprint`
+ `gsmCall`
+ `gsmSignal`
+ `sendSms`
+ `gsmVoice`
+ `powerAC`
+ `powerCapacity`
+ `networkSpeed`
+ `sensorSet`
+ `injectEmulatorCameraImage`
+ `isGpsEnabled`
+ `shake`
+ `clearApp`
+ `clearKeychains`
+ `configureLocalization`
+ `enrollBiometric`
+ `getPasteboard`
+ `installXCTestBundle`
+ `listXCTestBundles`
+ `listXCTestsInTestBundle`
+ `runXCTest`
+ `sendBiometricMatch`
+ `setPasteboard`
+ `setPermission`
+ `startAudioRecording`
+ `startLogsBroadcast`
+ `startRecordingScreen`
+ `startScreenStreaming`
+ `startXCTestScreenRecording`
+ `stopAudioRecording`
+ `stopLogsBroadcast`
+ `stopRecordingScreen`
+ `stopScreenStreaming`
+ `stopXCTestScreenRecording`
+ `updateSafariPreferences`