44. Environment setup, drivers, and device clouds

Goal

Why this matters

Prerequisites

1. Install automation servers and drivers

Windows

  1. Install WinAppDriver (https://github.com/microsoft/WinAppDriver). It registers itself in the Start menu and listens on http://127.0.0.1:4723.
  2. Ensure the machine is running in desktop interactive mode—WinAppDriver cannot interact with headless Windows Server sessions.
  3. Optional: pin the service to auto-start via schtasks or a Windows Service wrapper so CI agents bring it up automatically.

macOS

  1. Install Appium (npm install -g appium). For Appium 1, the built-in mac driver is sufficient; for Appium 2 install the mac2 driver (appium driver install mac2).
  2. Grant Xcode helper the accessibility permissions required to drive UI (see harness readme at external/Avalonia/tests/Avalonia.IntegrationTests.Appium/readme.md).
  3. Register your Avalonia app bundle so Appium can launch it by bundle ID. Avalonia’s script samples/IntegrationTestApp/bundle.sh builds and publishes the bundle.
  4. Start Appium. For Appium 2 use a base path to maintain compatibility with existing clients: appium --base-path=/wd/hub.

The harness toggles between Appium 1 and 2 using the IsRunningAppium2 property (external/Avalonia/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj:5). Set the property to true in Directory.Build.props or via dotnet test -p:IsRunningAppium2=true when running against Appium 2.

2. Package and register the test app

Appium launches desktop apps by path (Windows) or bundle identifier (macOS). The Avalonia sample uses IntegrationTestApp and rebuilds it before each run:

When testing your own app:

  1. Provide a CLI or script (PowerShell/bbash) that packs the app and exposes the absolute path or bundle ID through environment variables (TEST_APP_PATH, TEST_APP_BUNDLE).
  2. Inherit from DefaultAppFixture and override ConfigureWin32Options / ConfigureMacOptions to use those values. Example:
protected override void ConfigureWin32Options(AppiumOptions options, string? app = null)
{
    base.ConfigureWin32Options(options, Environment.GetEnvironmentVariable("TEST_APP_PATH"));
}

protected override void ConfigureMacOptions(AppiumOptions options, string? app = null)
{
    base.ConfigureMacOptions(options, Environment.GetEnvironmentVariable("TEST_APP_BUNDLE"));
}
  1. For variants (e.g., overlay popups), add command-line arguments via capabilities. OverlayPopupsAppFixture adds --overlayPopups on both platforms (external/Avalonia/tests/Avalonia.IntegrationTests.Appium/OverlayPopupsAppFixture.cs:4).

3. Start/stop lifecycle scripts

Automation servers must be running when tests start and shut down afterward. Avalonia’s pipelines demonstrate the sequence:

General guidelines:

4. Device cloud configuration

Device clouds (BrowserStack App Automate, Sauce Labs, Azure-hosted desktops) require the same capabilities plus authentication tokens:

options.AddAdditionalCapability("browserstack.user", Environment.GetEnvironmentVariable("BS_USER"));
options.AddAdditionalCapability("browserstack.key", Environment.GetEnvironmentVariable("BS_KEY"));
options.AddAdditionalCapability("appium:options", new Dictionary<string, object>
{
    ["osVersion"] = "11",
    ["deviceName"] = "Windows 11",
    ["appium:app"] = "bs://<uploaded-app-id>"
});

Upload your Avalonia app (packaged exe zipped, or macOS .app bundle) via the vendor’s CLI before tests run. On hosted Windows machines, ensure the automation provider exposes UI Automation trees—some locked-down images disable it.

When targeting clouds, keep these adjustments in fixtures:

protected override void ConfigureWin32Options(AppiumOptions options, string? app = null)
{
    if (UseCloud)
    {
        options.AddAdditionalCapability("app", CloudAppId);
        options.AddAdditionalCapability("bstack:options", new { osVersion = "11", sessionName = TestContext.CurrentContext.Test.Name });
    }
    else
    {
        base.ConfigureWin32Options(options, app);
    }
}

Guard cloud-specific behavior using environment variables so local runs stay unchanged.

5. Managing driver compatibility

The harness conditionally compiles for Appium 1 vs. 2 via APPIUM1/APPIUM2 constants (AppiumDriverEx.cs). Checklist:

If you see protocol errors, print the server log (appium.out) and compare capability names. Appium 2 requires appium: prefixes for vendor-specific entries (already shown in DefaultAppFixture.ConfigureMacOptions).

6. Permissions and security prompts

Desktop automation breaks when the app lacks accessibility permissions:

Automate these steps where possible—on macOS you can pre-provision a profile or run a script to enable permissions via tccutil. For Windows, prefer an image with WinAppDriver pre-installed.

7. Logging and diagnostics

Augment your harness to collect evidence:

8. Troubleshooting

Practice lab

  1. Bootstrap script – Create cross-platform scripts (scripts/run-appium-tests.ps1 and .sh) that build your app, start/stop automation servers, and invoke dotnet test. Validate they leave no background processes.
  2. Configurable fixture – Extend DefaultAppFixture to read capabilities from JSON (local vs. cloud). Add tests that assert the chosen configuration by inspecting Session.Capabilities.
  3. Permission audit – Write a checklist or automated probe that verifies accessibility permissions before starting tests (e.g., attempt to focus a dummy window and fail fast with instructions).
  4. Driver matrix – Run the same smoke suite against Appium 1 (WinAppDriver) and Appium 2 (mac2) by toggling IsRunningAppium2. Capture and compare server logs to understand protocol differences.
  5. CI integration – Add jobs to your pipeline that call your bootstrap script on Windows and macOS runners. Upload Appium logs and test TRX files as artifacts, confirming cleanup occurs even when tests fail.

What's next