Skip to content

Commit

Permalink
Merge pull request #20 from Redth/dev/interactive
Browse files Browse the repository at this point in the history
.NET Interactive support
  • Loading branch information
Redth authored Oct 14, 2022
2 parents b602c1d + bdf6242 commit ec88c37
Show file tree
Hide file tree
Showing 11 changed files with 617 additions and 100 deletions.
28 changes: 28 additions & 0 deletions samples/SampleInteractive-CI.dib
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!csharp

#i "nuget:C:\code\Maui.UITesting\src\packages"
#r "nuget:Redth.Microsoft.Maui.Automation.Interactive,0.10001.0"

#!markdown

`dotnet repl --run .\SampleInteractive-CI.dib --input platform="Android" device="Pixel_5_API_31" app="C:\code\Maui.UITesting\samples\SampleMauiApp\bin\Debug\net7.0-android\com.companyname.samplemauiapp-Signed.apk" --output-format trx --output-path .\testresults.trx --exit-after-run`

#!csharp

// Pixel_5_API_31
#!uitest --platform @input:platform --device @input:device --app @input:app

#!csharp

await Driver.First(By.AutomationId("entryUsername"))
.InputText("redth");

await Driver.First(By.AutomationId("entryPassword"))
.InputText("1234");

await Driver.First(By.ContainingText("Login"))
.Tap();

#!csharp

await Driver.RenderScreenshot();
37 changes: 37 additions & 0 deletions samples/SampleInteractive.dib
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!pwsh

Remove-Item -Recurse -Force ~/.nuget/packages/redth.microsoft.maui.automation.interactive
Remove-Item -Recurse -Force ~/.nuget/packages/redth.microsoft.maui.automation
Remove-Item -Recurse -Force ~/.nuget/packages/redth.microsoft.maui.automation.driver

$pkgVersion="0.10001"
dotnet pack ../src/Core/Core.csproj -p:PackageVersion=$pkgVersion -p:PackageOutputPath=../packages
dotnet pack ../src/Driver/Driver.csproj -p:PackageVersion=$pkgVersion -p:PackageOutputPath=../packages
dotnet pack ../src/Agent/Agent.csproj -p:PackageVersion=$pkgVersion -p:PackageOutputPath=../packages
dotnet pack ../src/Interactive/Interactive.csproj -p:PackageVersion=$pkgVersion -p:PackageOutputPath=../packages

#!csharp

#i "nuget:C:\code\Maui.UITesting\src\packages"
#r "nuget:Redth.Microsoft.Maui.Automation.Interactive,0.10001.0"

#!csharp

#!uitest --platform Android --device Pixel_5_API_31 --app "C:\code\Maui.UITesting\samples\SampleMauiApp\bin\Debug\net7.0-android\com.companyname.samplemauiapp-Signed.apk"

#!csharp

await Driver.Start();

await Driver.First(By.AutomationId("entryUsername"))
.InputText("redth");

await Driver.First(By.AutomationId("entryPassword"))
.InputText("1234");

await Driver.First(By.ContainingText("Login"))
.Tap();

#!csharp

await Driver.RenderScreenshot();
16 changes: 13 additions & 3 deletions src/Driver/AndroidDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -327,10 +327,20 @@ public override Task<string[]> Backdoor(Platform automationPlatform, string full

public override Task Screenshot(string? filename = null)
{
var fullFilename = base.GetScreenshotFilename(filename);
var fullFilename = base.GetScreenshotFilename(filename);
var localDir = Path.Combine(Path.GetTempPath(), "adbshellpull");
Directory.CreateDirectory(localDir);

var remoteFile = $"/sdcard/{Guid.NewGuid().ToString("N")}.png";

Shell($"screencap {remoteFile}");

WrapAdbTool(() => Adb.Run("-s", $"\"{Device}\"", "pull", remoteFile, localDir));

File.Move(Path.Combine(localDir, Path.GetFileName(remoteFile)), fullFilename, true);

Shell($"rm {remoteFile}");

WrapAdbTool(() =>
Adb.ScreenCapture(new FileInfo(fullFilename), Device));
return Task.CompletedTask;
}

Expand Down
6 changes: 3 additions & 3 deletions src/Driver/AppDriverBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public static AppDriverBuilder WithConfig(string configFile)

public readonly IAutomationConfiguration Configuration;

readonly IHostBuilder HostBuilder;
IHost? Host;
protected readonly IHostBuilder HostBuilder;
protected IHost? Host;

public AppDriverBuilder()
{
Expand Down Expand Up @@ -82,7 +82,7 @@ public AppDriverBuilder ConfigureLogging(Action<ILoggingBuilder> configure)
return this;
}

public IDriver Build()
public virtual IDriver Build()
{
Host = HostBuilder.Build();

Expand Down
16 changes: 13 additions & 3 deletions src/Driver/AutomationConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,19 @@ public AutomationConfiguration(string appId, string appFilename, Platform device
if (!string.IsNullOrEmpty(device))
Device = device;
AutomationPlatform = automationPlatform ?? devicePlatform;
}

public string? AppAgentAddress
}

public AutomationConfiguration(string appId, FileInfo appFilename, Platform devicePlatform, string? device = null, Platform? automationPlatform = null)
{
AppFilename = appFilename.FullName;
AppId = appId;
DevicePlatform = devicePlatform;
if (!string.IsNullOrEmpty(device))
Device = device;
AutomationPlatform = automationPlatform ?? devicePlatform;
}

public string? AppAgentAddress
{
get => GetOrDefault(nameof(AppAgentAddress), IPAddress.Loopback.ToString())?.ToString();
set => Set(nameof(AppAgentAddress), value);
Expand Down
218 changes: 218 additions & 0 deletions src/Driver/Xcode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Maui.Automation.Driver;
using Newtonsoft.Json;

namespace Microsoft.Maui.Automation.Util;

public class Xcode
{
public static List<DeviceData> GetDevices(params string[] targetPlatformIdentifiers)
{
var xcode = GetBestXcode();

var xcdevice = new FileInfo(Path.Combine(xcode, "Contents/Developer/usr/bin/xcdevice"));

if (!xcdevice.Exists)
throw new FileNotFoundException(xcdevice.FullName);
var pr = new ProcessRunner(NullLogger.Instance, xcdevice.FullName, "list");

var json = string.Join(Environment.NewLine, pr.Output);


var xcdevices = JsonConvert.DeserializeObject<List<XcDevice>>(json);

var tpidevices = xcdevices
.Where(d => (targetPlatformIdentifiers == null || targetPlatformIdentifiers.Length <= 0)
|| (targetPlatformIdentifiers.Intersect(d.DotNetPlatforms)?.Any() ?? false));

var filteredDevices = tpidevices
.Select(d => new DeviceData
{
IsEmulator = d.Simulator,
IsRunning = false,
Name = d.Name,
Details = d.ModelName + " (" + d.Architecture + ")",
Platforms = d.DotNetPlatforms,
Serial = d.Identifier,
Version = d.OperatingSystemVersion
});

return filteredDevices.ToList();
}

internal static string GetBestXcode()
{
var selected = GetSelectedXCodePath();

if (!string.IsNullOrEmpty(selected))
return selected;

return FindXCodeInstalls()?.FirstOrDefault();
}

static string? GetSelectedXCodePath()
{
var r = new ProcessRunner(NullLogger.Instance, "/usr/bin/xcode-select", "-p");
var xcodeSelectedPath = string.Join(Environment.NewLine, r.Output)?.Trim();

if (!string.IsNullOrEmpty(xcodeSelectedPath))
{
var infoPlist = Path.Combine(xcodeSelectedPath, "..", "Info.plist");
if (File.Exists(infoPlist))
{
var info = GetXcodeInfo(
Path.GetFullPath(
Path.Combine(xcodeSelectedPath, "..", "..")), true);

if (info != null)
return info?.Path;
}
}

return null;
}

static readonly string[] LikelyPaths = new[]
{
"/Applications/Xcode.app",
"/Applications/Xcode-beta.app",
};

static IEnumerable<string> FindXCodeInstalls()
{
foreach (var p in LikelyPaths)
{
var i = GetXcodeInfo(p, false)?.Path;
if (i != null)
yield return i;
}
}

static (string Path, bool Selected)? GetXcodeInfo(string path, bool selected)
{
var versionPlist = Path.Combine(path, "Contents", "version.plist");

if (File.Exists(versionPlist))
{
return (path, selected);
}
else
{
var infoPlist = Path.Combine(path, "Contents", "Info.plist");

if (File.Exists(infoPlist))
{
return (path, selected);
}
}
return null;
}
}

public class XcDevice
{

public const string PlatformMacOsx = "com.apple.platform.macosx";
public const string PlatformiPhoneSimulator = "com.apple.platform.iphonesimulator";
public const string PlatformAppleTvSimulator = "com.apple.platform.appletvsimulator";
public const string PlatformAppleTv = "com.apple.platform.appletvos";
public const string PlatformWatchSimulator = "com.apple.platform.watchsimulator";
public const string PlatformiPhone = "com.apple.platform.iphoneos";
public const string PlatformWatch = "com.apple.platform.watchos";

[JsonProperty("simulator")]
public bool Simulator { get; set; }

[JsonProperty("operatingSystemVersion")]
public string OperatingSystemVersion { get; set; }

[JsonProperty("available")]
public bool Available { get; set; }

[JsonProperty("platform")]
public string Platform { get; set; }

public bool IsiOS
=> !string.IsNullOrEmpty(Platform) && (Platform.Equals(PlatformiPhone) || Platform.Equals(PlatformiPhoneSimulator));
public bool IsTvOS
=> !string.IsNullOrEmpty(Platform) && (Platform.Equals(PlatformAppleTv) || Platform.Equals(PlatformAppleTvSimulator));
public bool IsWatchOS
=> !string.IsNullOrEmpty(Platform) && (Platform.Equals(PlatformWatch) || Platform.Equals(PlatformWatchSimulator));
public bool IsOsx
=> !string.IsNullOrEmpty(Platform) && Platform.Equals(PlatformMacOsx);

public string[] DotNetPlatforms
=> Platform switch
{
PlatformiPhone => new[] { "ios" },
PlatformiPhoneSimulator => new[] { "ios" },
PlatformAppleTv => new[] { "tvos" },
PlatformAppleTvSimulator => new[] { "tvos" },
PlatformWatch => new[] { "watchos" },
PlatformWatchSimulator => new[] { "watchos" },
PlatformMacOsx => new[] { "macos", "maccatalyst" },
_ => new string[0]
};

[JsonProperty("modelCode")]
public string ModelCode { get; set; }

[JsonProperty("identifier")]
public string Identifier { get; set; }

[JsonProperty("architecture")]
public string Architecture { get; set; }

public string RuntimeIdentifier
=> Architecture switch
{
_ => "iossimulator-x64"
};

[JsonProperty("modelUTI")]
public string ModelUTI { get; set; }

[JsonProperty("modelName")]
public string ModelName { get; set; }

[JsonProperty("name")]
public string Name { get; set; }

[JsonProperty("interface")]
public string Interface { get; set; }
}

public class DeviceData
{
[JsonProperty("name")]
public string Name { get; set; }

[JsonProperty("details")]
public string Details { get; set; }

[JsonProperty("serial")]
public string Serial { get; set; }

[JsonProperty("platforms")]
public string[] Platforms { get; set; }

[JsonProperty("version")]
public string Version { get; set; }

[JsonProperty("isEmulator")]
public bool IsEmulator { get; set; }

[JsonProperty("isRunning")]
public bool IsRunning { get; set; }

[JsonProperty("rid")]
public string RuntimeIdentifier { get; set; }
}
Loading

0 comments on commit ec88c37

Please sign in to comment.