Run the sample native app visual test
Step 1: Setup environment
Requirements
- .NET 9.0
- NUnit framework
- Appium client
- Access key
- You can generate an Access key using the following instruction – Access key
- Billing unit
- The billing unit is your team’s unique identifier, or just the word “personal” in the case of an individual account.
More information can be found here.
- The billing unit is your team’s unique identifier, or just the word “personal” in the case of an individual account.
Step 2: Upload and install an App
Before running tests, several preparations are required:
- Upload the app (only once).
- Find and take a Device
- Install the App
All of these actions can be performed automatically via API.
For more details, please refer to the Upload and Install document.
Step 3: Add the interaction with visual testing API
After setup up the environment, you need to add the interaction with the visual testing API:
using NUnit.Framework;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Drawing;
namespace AppiumTests
{
public class VisualBuildInputData
{
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("project")]
public string Project { get; set; }
[JsonPropertyName("branch")]
public string Branch { get; set; }
}
public class VisualSnapshotInputData
{
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("buildId")]
public string BuildId { get; set; }
[JsonPropertyName("image")]
public string Image { get; set; } // Path to the image file
[JsonPropertyName("testName")]
public string TestName { get; set; }
[JsonPropertyName("suiteName")]
public string SuiteName { get; set; }
[JsonPropertyName("device")]
public string Device { get; set; }
}
public class VisualBuildData
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("project")]
public string Project { get; set; }
[JsonPropertyName("branch")]
public string Branch { get; set; }
[JsonPropertyName("status")]
public string Status { get; set; }
}
public class VisualSnapshotData
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("buildId")]
public string BuildId { get; set; }
[JsonPropertyName("testName")]
public string TestName { get; set; }
[JsonPropertyName("suiteName")]
public string SuiteName { get; set; }
[JsonPropertyName("device")]
public string Device { get; set; }
}
public class VisualTestingAPI
{
private readonly HttpClient _httpClient;
public VisualTestingAPI(string baseUrl, string apiKey)
{
_httpClient = new HttpClient
{
BaseAddress = new Uri(baseUrl)
};
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);;
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public VisualBuildData CreateBuild(VisualBuildInputData inputData)
{
var jsonContent = JsonSerializer.Serialize(inputData);
var stringContent = new StringContent(jsonContent, System.Text.Encoding.UTF8, "application/json");
var response = _httpClient.PostAsync("builds", stringContent).Result; // Wait for response synchronously
response.EnsureSuccessStatusCode();
var responseContent = response.Content.ReadAsStringAsync().Result;
return JsonSerializer.Deserialize<VisualBuildData>(responseContent);
}
public void FinishBuild(string buildId)
{
var response = _httpClient.PostAsync($"builds/{buildId}/finish", null).Result; // Wait synchronously
response.EnsureSuccessStatusCode();
Console.WriteLine("Build finished successfully");
}
public VisualSnapshotData SubmitSnapshot(VisualSnapshotInputData inputData)
{
// Prepare Multipart/Form-Data content
var formContent = new MultipartFormDataContent
{
{ new StringContent(inputData.Name), "name" },
{ new StringContent(inputData.TestName), "testName" },
{ new StringContent(inputData.SuiteName), "suiteName" },
{ new StringContent(inputData.Device), "device" }
};
// Attach image file
if (File.Exists(inputData.Image))
{
var fileStream = new FileStream(inputData.Image, FileMode.Open, FileAccess.Read);
var fileContent = new StreamContent(fileStream);
fileContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
formContent.Add(fileContent, "image", Path.GetFileName(inputData.Image));
}
else
{
throw new FileNotFoundException("Image file not found.", inputData.Image);
}
var response = _httpClient.PostAsync($"builds/{inputData.BuildId}/snapshots", formContent).Result; // Wait synchronously
response.EnsureSuccessStatusCode();
var responseContent = response.Content.ReadAsStringAsync().Result;
return JsonSerializer.Deserialize<VisualSnapshotData>(responseContent);
}
}
}Step 4: Run sample test
After setting up the environment, installing the native App, and integrating the visual testing API, you can run your first native visual test
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.iOS;
using OpenQA.Selenium.Support.UI;
using System;
namespace AppiumTests
{
public class IOsNativeTest
{
// Constants
const string ProjectName = "<TEAM_CODE>";
const string AccessKey = "mobitru_ak_......";
const string AppiumHost = "app.mobitru.com";
const string VisuaTestingHost = "visual.mobitru.com";
const string VisualTestingBaseUrl = $"""https://{VisuaTestingHost}/billing/unit/{ProjectName}/api/v1/""";
const string DeviceUdid = "00008101-001608482602001E";
const string AppBundleId = "com.epam.mobitru.demoapp";
const int DefaultImplicitTimeoutSec = 3;
const int DefaultWaitTimeoutSec = 10;
const string AppUserName = "testuser@mobitru.com";
const string AppUserPassword = "password1";
const string VisualTestName = "visual c# demo for ios";
private IOSDriver driver;
private VisualBuildData visualBuildData;
private readonly VisualTestingAPI visualTestingApi = new VisualTestingAPI(VisualTestingBaseUrl, AccessKey);
[SetUp]
public void Setup()
{
// Step 1: Create a visual build
var buildInput = new VisualBuildInputData
{
Name = "Build 1",
Project = "C# iOS Native Automation Demo",
Branch = "main"
};
visualBuildData = visualTestingApi.CreateBuild(buildInput);
var appiumOptions = new AppiumOptions();
appiumOptions.AutomationName = "XCUITest";
appiumOptions.AddAdditionalAppiumOption("udid", DeviceUdid);
appiumOptions.AddAdditionalAppiumOption("bundleId", AppBundleId);
var appiumHubUrl = $"""https://{ProjectName}:{AccessKey}@{AppiumHost}/wd/hub""";
driver = new IOSDriver(new Uri($"{appiumHubUrl}"), appiumOptions);
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(DefaultImplicitTimeoutSec);
}
[Test]
public void TestAppiumDemo()
{
// enter details
driver.FindElement(By.XPath("//XCUIElementTypeTextField[starts-with(@name,'Login')]"))
.SendKeys(AppUserName);
TakeAndSubmitScreenshot(driver, "after_enter_login", VisualTestName, visualBuildData, visualTestingApi);
driver.FindElement(By.XPath("//XCUIElementTypeSecureTextField[starts-with(@name,'Password')]"))
.SendKeys(AppUserPassword);
TakeAndSubmitScreenshot(driver, "after_enter_password", VisualTestName, visualBuildData, visualTestingApi);
// submit login screen
driver.FindElement(By.XPath("//XCUIElementTypeButton[starts-with(@name,'Sign in')]")).Click();
// wait for next screen
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(DefaultWaitTimeoutSec));
By productHeaderElement = By.XPath("//*[starts-with(@name,'productHeaderViewLabel')]");
wait.Until(d => d.FindElement(productHeaderElement).Displayed);
TakeAndSubmitScreenshot(driver, "after_login", VisualTestName, visualBuildData, visualTestingApi);
}
[TearDown]
public void TearDown()
{
visualTestingApi.FinishBuild(visualBuildData.Id);
driver?.Quit();
}
private void TakeAndSubmitScreenshot(IOSDriver driver, String name, String testName, VisualBuildData buildData, VisualTestingAPI visualTestingApi)
{
var serial = driver.Capabilities.GetCapability("udid").ToString();
string fileName = System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".png";
driver.GetScreenshot().SaveAsFile(fileName);
var snapshotInput = new VisualSnapshotInputData
{
BuildId = buildData.Id,
Name = name,
TestName = testName,
SuiteName = buildData.Project,
Device = serial,
Image = fileName
};
var snapshotData = visualTestingApi.SubmitSnapshot(snapshotInput);
Console.WriteLine($"Snapshot Submitted: {snapshotData.Id}");
}
}
}