From f128a6d04173bba074687b33134112a0a70bac5a Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Sat, 31 Aug 2024 02:11:09 +0800 Subject: [PATCH] Added lots more code --- DeviceRunners.sln | 29 +++++- .../UnitTest1.cs | 15 --- ...ngKitApp.UITests.XunitTests.Android.csproj | 5 - .../AppiumServerTests.cs | 24 +++++ .../BaseUITests.cs | 65 +++++++++++++ ...iceTestingKitApp.UITests.NUnitTests.csproj | 4 +- .../UITestsSetupFixture.cs | 43 +++++++++ ...ngKitApp.UITests.XunitTests.Windows.csproj | 5 - ...ngKitApp.UITests.XunitTests.Android.csproj | 5 + .../_Config.cs | 2 +- ...ngKitApp.UITests.XunitTests.Windows.csproj | 5 + .../_Config.cs | 2 +- .../AppiumServerTests.cs | 11 +-- .../BaseUITests.cs | 31 +------ ...iceTestingKitApp.UITests.XunitTests.csproj | 0 ...eTestingKitApp.UITests.XunitTests.targets} | 2 +- .../MainPageTests.cs | 18 ++-- .../UITestsFixture.cs | 2 +- .../_Config.Common.cs | 2 +- .../xunit.runner.json | 0 .../AppiumAutomatedApp.cs | 55 +++++------ .../AppiumAutomatedAppElement.cs | 18 ++++ .../AppiumAutomatedAppOptions.cs | 9 +- .../AppiumAutomatedAppOptionsBuilder.cs | 6 +- .../AppiumBy.cs | 32 +++++++ .../AppiumByFactory.cs | 6 ++ .../AndroidAppiumAutomatedAppOptions.cs | 4 +- ...AndroidAppiumAutomatedAppOptionsBuilder.cs | 2 +- .../WindowsAppiumAutomatedAppOptions.cs | 4 +- ...WindowsAppiumAutomatedAppOptionsBuilder.cs | 2 +- .../Windows/WindowsAppiumBy.cs | 13 +++ .../Windows/WindowsAppiumByFactory.cs | 6 ++ .../AndroidAppiumDismissKeyboardCommand.cs | 2 +- ...iumAutomatedAppOptionsBuilderExtensions.cs | 3 + .../Commands/AppiumClickCoordinatesCommand.cs | 32 +++++++ .../Commands/AppiumClickElementCommand.cs | 34 +++++++ .../Commands/AppiumCommandNames.cs | 8 -- .../Commands/AppiumCommonCommandNames.cs | 6 ++ .../Commands/AppiumElementCommand .cs | 24 +++++ .../Commands/AppiumGetElementTextCommand.cs | 14 +++ .../Commands/AppiumGetPageSourceCommand.cs | 2 +- .../Commands/AppiumGetScreenshotCommand.cs | 2 +- .../Commands/AutomatedAppExtensions.cs | 8 +- .../DriverManager/AppiumDriverManager.cs | 2 +- .../IAppiumByFactory.cs | 6 ++ .../AutomationTestSuite.cs | 21 ++++- .../AutomationTestSuiteBuilder.cs | 6 +- .../ByExtensions.cs | 8 ++ src/DeviceRunners.UIAutomation/BySelectors.cs | 7 ++ ...cutor.cs => AutomatedAppCommandManager.cs} | 7 +- .../AutomatedAppCommandManagerExtensions.cs | 7 ++ .../Commands/AutomatedAppElementExtensions.cs | 13 +++ .../Commands/AutomatedAppExtensions.cs | 16 ++++ .../Commands/CommonCommandNames.cs | 11 +++ .../IAutomatedApp.cs | 2 +- .../IAutomatedAppElement.cs | 6 ++ src/DeviceRunners.UIAutomation/IBy.cs | 6 ++ .../IContainsElements.cs | 8 ++ .../AutomationTestSuiteBuilderTests.cs | 51 ++++++++++ ...ceRunners.UIAutomation.Appium.Tests.csproj | 22 +++++ .../AutomationTestSuiteBuilderTests.cs | 60 ++++++++++++ .../AutomationTestSuiteTests.cs | 92 +++++++++++++++++++ .../AutomatedAppCommandManagerTests.cs | 86 +++++++++++++++++ .../Commands/AutomatedAppCommandTests.cs | 82 +++++++++++++++++ .../AutomatedAppOptionsBuilderExtensions.cs | 30 ++++++ .../DeviceRunners.UIAutomation.Tests.csproj | 21 +++++ 66 files changed, 992 insertions(+), 140 deletions(-) delete mode 100644 DeviceTestingKitApp.UITests.NUnitTests/UnitTest1.cs delete mode 100644 sample/test/DeviceTestingKitApp.UITests.Android/DeviceTestingKitApp.UITests.XunitTests.Android.csproj create mode 100644 sample/test/DeviceTestingKitApp.UITests.NUnitTests/AppiumServerTests.cs create mode 100644 sample/test/DeviceTestingKitApp.UITests.NUnitTests/BaseUITests.cs rename {DeviceTestingKitApp.UITests.NUnitTests => sample/test/DeviceTestingKitApp.UITests.NUnitTests}/DeviceTestingKitApp.UITests.NUnitTests.csproj (76%) create mode 100644 sample/test/DeviceTestingKitApp.UITests.NUnitTests/UITestsSetupFixture.cs delete mode 100644 sample/test/DeviceTestingKitApp.UITests.Windows/DeviceTestingKitApp.UITests.XunitTests.Windows.csproj create mode 100644 sample/test/DeviceTestingKitApp.UITests.XunitTests.Android/DeviceTestingKitApp.UITests.XunitTests.Android.csproj rename sample/test/{DeviceTestingKitApp.UITests.Android => DeviceTestingKitApp.UITests.XunitTests.Android}/_Config.cs (75%) create mode 100644 sample/test/DeviceTestingKitApp.UITests.XunitTests.Windows/DeviceTestingKitApp.UITests.XunitTests.Windows.csproj rename sample/test/{DeviceTestingKitApp.UITests.Windows => DeviceTestingKitApp.UITests.XunitTests.Windows}/_Config.cs (75%) rename sample/test/{DeviceTestingKitApp.UITests => DeviceTestingKitApp.UITests.XunitTests}/AppiumServerTests.cs (66%) rename sample/test/{DeviceTestingKitApp.UITests => DeviceTestingKitApp.UITests.XunitTests}/BaseUITests.cs (58%) rename sample/test/{DeviceTestingKitApp.UITests => DeviceTestingKitApp.UITests.XunitTests}/DeviceTestingKitApp.UITests.XunitTests.csproj (100%) rename sample/test/{DeviceTestingKitApp.UITests/DeviceTestingKitApp.UITests.targets => DeviceTestingKitApp.UITests.XunitTests/DeviceTestingKitApp.UITests.XunitTests.targets} (93%) rename sample/test/{DeviceTestingKitApp.UITests => DeviceTestingKitApp.UITests.XunitTests}/MainPageTests.cs (60%) rename sample/test/{DeviceTestingKitApp.UITests => DeviceTestingKitApp.UITests.XunitTests}/UITestsFixture.cs (96%) rename sample/test/{DeviceTestingKitApp.UITests => DeviceTestingKitApp.UITests.XunitTests}/_Config.Common.cs (90%) rename sample/test/{DeviceTestingKitApp.UITests => DeviceTestingKitApp.UITests.XunitTests}/xunit.runner.json (100%) create mode 100644 src/DeviceRunners.UIAutomation.Appium/AppiumAutomatedAppElement.cs create mode 100644 src/DeviceRunners.UIAutomation.Appium/AppiumBy.cs create mode 100644 src/DeviceRunners.UIAutomation.Appium/AppiumByFactory.cs create mode 100644 src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Windows/WindowsAppiumBy.cs create mode 100644 src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Windows/WindowsAppiumByFactory.cs create mode 100644 src/DeviceRunners.UIAutomation.Appium/Commands/AppiumClickCoordinatesCommand.cs create mode 100644 src/DeviceRunners.UIAutomation.Appium/Commands/AppiumClickElementCommand.cs delete mode 100644 src/DeviceRunners.UIAutomation.Appium/Commands/AppiumCommandNames.cs create mode 100644 src/DeviceRunners.UIAutomation.Appium/Commands/AppiumCommonCommandNames.cs create mode 100644 src/DeviceRunners.UIAutomation.Appium/Commands/AppiumElementCommand .cs create mode 100644 src/DeviceRunners.UIAutomation.Appium/Commands/AppiumGetElementTextCommand.cs create mode 100644 src/DeviceRunners.UIAutomation.Appium/IAppiumByFactory.cs create mode 100644 src/DeviceRunners.UIAutomation/ByExtensions.cs create mode 100644 src/DeviceRunners.UIAutomation/BySelectors.cs rename src/DeviceRunners.UIAutomation/Commands/{AutomatedAppCommandExecutor.cs => AutomatedAppCommandManager.cs} (70%) create mode 100644 src/DeviceRunners.UIAutomation/Commands/AutomatedAppCommandManagerExtensions.cs create mode 100644 src/DeviceRunners.UIAutomation/Commands/AutomatedAppElementExtensions.cs create mode 100644 src/DeviceRunners.UIAutomation/Commands/AutomatedAppExtensions.cs create mode 100644 src/DeviceRunners.UIAutomation/Commands/CommonCommandNames.cs create mode 100644 src/DeviceRunners.UIAutomation/IAutomatedAppElement.cs create mode 100644 src/DeviceRunners.UIAutomation/IBy.cs create mode 100644 src/DeviceRunners.UIAutomation/IContainsElements.cs create mode 100644 test/DeviceRunners.UIAutomation.Appium.Tests/AutomationTestSuiteBuilderTests.cs create mode 100644 test/DeviceRunners.UIAutomation.Appium.Tests/DeviceRunners.UIAutomation.Appium.Tests.csproj create mode 100644 test/DeviceRunners.UIAutomation.Tests/AutomationTestSuiteBuilderTests.cs create mode 100644 test/DeviceRunners.UIAutomation.Tests/AutomationTestSuiteTests.cs create mode 100644 test/DeviceRunners.UIAutomation.Tests/Commands/AutomatedAppCommandManagerTests.cs create mode 100644 test/DeviceRunners.UIAutomation.Tests/Commands/AutomatedAppCommandTests.cs create mode 100644 test/DeviceRunners.UIAutomation.Tests/Commands/AutomatedAppOptionsBuilderExtensions.cs create mode 100644 test/DeviceRunners.UIAutomation.Tests/DeviceRunners.UIAutomation.Tests.csproj diff --git a/DeviceRunners.sln b/DeviceRunners.sln index ae650b1..b938eae 100644 --- a/DeviceRunners.sln +++ b/DeviceRunners.sln @@ -51,17 +51,23 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7FD9E644-B76 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{EE37724C-5FEF-46BB-AD80-CD002CC7FF54}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceTestingKitApp.UITests.XunitTests", "sample\test\DeviceTestingKitApp.UITests\DeviceTestingKitApp.UITests.XunitTests.csproj", "{7F8A6736-38CC-42DE-A609-399FC3C7FFEA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceTestingKitApp.UITests.XunitTests", "sample\test\DeviceTestingKitApp.UITests.XunitTests\DeviceTestingKitApp.UITests.XunitTests.csproj", "{7F8A6736-38CC-42DE-A609-399FC3C7FFEA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceTestingKitApp.UITests.XunitTests.Windows", "sample\test\DeviceTestingKitApp.UITests.Windows\DeviceTestingKitApp.UITests.XunitTests.Windows.csproj", "{C2A34B21-6334-4B31-9617-D1648C6B0A50}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceTestingKitApp.UITests.XunitTests.Windows", "sample\test\DeviceTestingKitApp.UITests.XunitTests.Windows\DeviceTestingKitApp.UITests.XunitTests.Windows.csproj", "{C2A34B21-6334-4B31-9617-D1648C6B0A50}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceTestingKitApp.UITests.XunitTests.Android", "sample\test\DeviceTestingKitApp.UITests.Android\DeviceTestingKitApp.UITests.XunitTests.Android.csproj", "{34618A8D-7BA5-4999-8049-3220DC63648C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceTestingKitApp.UITests.XunitTests.Android", "sample\test\DeviceTestingKitApp.UITests.XunitTests.Android\DeviceTestingKitApp.UITests.XunitTests.Android.csproj", "{34618A8D-7BA5-4999-8049-3220DC63648C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceRunners.UIAutomation.Appium", "src\DeviceRunners.UIAutomation.Appium\DeviceRunners.UIAutomation.Appium.csproj", "{B1A52689-AE30-4136-9502-AD2B6A6D154A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceRunners.UIAutomation", "src\DeviceRunners.UIAutomation\DeviceRunners.UIAutomation.csproj", "{4B514D48-D89C-457D-B2DE-F7822A7225B9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceTestingKitApp.UITests.NUnitTests", "DeviceTestingKitApp.UITests.NUnitTests\DeviceTestingKitApp.UITests.NUnitTests.csproj", "{03A0F40A-9234-4DCE-AC34-0A1138525DD4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceTestingKitApp.UITests.NUnitTests", "sample\test\DeviceTestingKitApp.UITests.NUnitTests\DeviceTestingKitApp.UITests.NUnitTests.csproj", "{03A0F40A-9234-4DCE-AC34-0A1138525DD4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceRunners.UIAutomation.Selenium", "src\DeviceRunners.UIAutomation.Selenium\DeviceRunners.UIAutomation.Selenium.csproj", "{CBF6C8E0-681A-426C-9A44-91125AB3208C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceRunners.UIAutomation.Tests", "test\DeviceRunners.UIAutomation.Tests\DeviceRunners.UIAutomation.Tests.csproj", "{7A1A6BC4-CDAF-4A5F-8CAD-85E387F7B64F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceRunners.UIAutomation.Appium.Tests", "test\DeviceRunners.UIAutomation.Appium.Tests\DeviceRunners.UIAutomation.Appium.Tests.csproj", "{5C4E5D7E-B768-4C68-A94A-27EFEE9E20E1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -173,6 +179,18 @@ Global {03A0F40A-9234-4DCE-AC34-0A1138525DD4}.Debug|Any CPU.Build.0 = Debug|Any CPU {03A0F40A-9234-4DCE-AC34-0A1138525DD4}.Release|Any CPU.ActiveCfg = Release|Any CPU {03A0F40A-9234-4DCE-AC34-0A1138525DD4}.Release|Any CPU.Build.0 = Release|Any CPU + {CBF6C8E0-681A-426C-9A44-91125AB3208C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CBF6C8E0-681A-426C-9A44-91125AB3208C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CBF6C8E0-681A-426C-9A44-91125AB3208C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CBF6C8E0-681A-426C-9A44-91125AB3208C}.Release|Any CPU.Build.0 = Release|Any CPU + {7A1A6BC4-CDAF-4A5F-8CAD-85E387F7B64F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7A1A6BC4-CDAF-4A5F-8CAD-85E387F7B64F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7A1A6BC4-CDAF-4A5F-8CAD-85E387F7B64F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7A1A6BC4-CDAF-4A5F-8CAD-85E387F7B64F}.Release|Any CPU.Build.0 = Release|Any CPU + {5C4E5D7E-B768-4C68-A94A-27EFEE9E20E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C4E5D7E-B768-4C68-A94A-27EFEE9E20E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C4E5D7E-B768-4C68-A94A-27EFEE9E20E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C4E5D7E-B768-4C68-A94A-27EFEE9E20E1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -205,6 +223,9 @@ Global {B1A52689-AE30-4136-9502-AD2B6A6D154A} = {DBF1C119-C96C-4DD3-810B-52451603475C} {4B514D48-D89C-457D-B2DE-F7822A7225B9} = {DBF1C119-C96C-4DD3-810B-52451603475C} {03A0F40A-9234-4DCE-AC34-0A1138525DD4} = {EE37724C-5FEF-46BB-AD80-CD002CC7FF54} + {CBF6C8E0-681A-426C-9A44-91125AB3208C} = {DBF1C119-C96C-4DD3-810B-52451603475C} + {7A1A6BC4-CDAF-4A5F-8CAD-85E387F7B64F} = {5073EAFD-FF7B-4C4E-84D2-3AA38A91DE73} + {5C4E5D7E-B768-4C68-A94A-27EFEE9E20E1} = {5073EAFD-FF7B-4C4E-84D2-3AA38A91DE73} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2DABD41C-B083-429E-AE1E-971CFF8DC199} diff --git a/DeviceTestingKitApp.UITests.NUnitTests/UnitTest1.cs b/DeviceTestingKitApp.UITests.NUnitTests/UnitTest1.cs deleted file mode 100644 index 693e87a..0000000 --- a/DeviceTestingKitApp.UITests.NUnitTests/UnitTest1.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace DeviceTestingKitApp.UITests.NUnitTests; - -public class Tests -{ - [SetUp] - public void Setup() - { - } - - [Test] - public void Test1() - { - Assert.Pass(); - } -} diff --git a/sample/test/DeviceTestingKitApp.UITests.Android/DeviceTestingKitApp.UITests.XunitTests.Android.csproj b/sample/test/DeviceTestingKitApp.UITests.Android/DeviceTestingKitApp.UITests.XunitTests.Android.csproj deleted file mode 100644 index 15b0be5..0000000 --- a/sample/test/DeviceTestingKitApp.UITests.Android/DeviceTestingKitApp.UITests.XunitTests.Android.csproj +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/sample/test/DeviceTestingKitApp.UITests.NUnitTests/AppiumServerTests.cs b/sample/test/DeviceTestingKitApp.UITests.NUnitTests/AppiumServerTests.cs new file mode 100644 index 0000000..fb5ecce --- /dev/null +++ b/sample/test/DeviceTestingKitApp.UITests.NUnitTests/AppiumServerTests.cs @@ -0,0 +1,24 @@ +using NUnit.Framework.Internal; + +namespace DeviceTestingKitApp.UITests.NUnitTests; + +public class AppiumServerTests : BaseUITests +{ + public AppiumServerTests(string appKey) + : base(appKey) + { + } + + [Test] + public void IsReady() + { + //var id = Driver.SessionId; + + //Assert.NotNull(id); + //Assert.NotEmpty(id.ToString()); + + //Assert.Equal(AppState.RunningInForeground, Driver.GetAppState()); + + Assert.Pass(); + } +} diff --git a/sample/test/DeviceTestingKitApp.UITests.NUnitTests/BaseUITests.cs b/sample/test/DeviceTestingKitApp.UITests.NUnitTests/BaseUITests.cs new file mode 100644 index 0000000..5b4653f --- /dev/null +++ b/sample/test/DeviceTestingKitApp.UITests.NUnitTests/BaseUITests.cs @@ -0,0 +1,65 @@ +using System.Xml.Linq; + +using DeviceRunners.UIAutomation; +using DeviceRunners.UIAutomation.Appium; + +using NUnit.Framework.Internal; + +namespace DeviceTestingKitApp.UITests.NUnitTests; + +[TestFixture("android")] +[TestFixture("windows")] +[Parallelizable(ParallelScope.All)] +public abstract class BaseUITests +{ + private readonly string _testKey; + + private AutomationTestSuite _automationTestSuite; + private IAutomatedApp _app; + + public BaseUITests(string testKey) + { + _testKey = testKey; + } + + protected IAutomatedApp App => _app; + + protected TextWriter Output => TestContext.Out; + + [SetUp] + public void SetUp() + { + _automationTestSuite = UITestsSetupFixture.TestSuite; + _app = _automationTestSuite.StartApp(_testKey); + + //DeviceBy = new UITestsDeviceBy(Driver); + } + + [TearDown] + public void TearDown() + { + if (App?.GetPageSource() is string source && !string.IsNullOrWhiteSpace(source)) + Output.WriteLine($"Last page source:{Environment.NewLine}{XDocument.Parse(source)}"); + else + Output.WriteLine("Page source is empty"); + + if (App?.GetScreenshot() is { } screenshot) + Output.WriteLine($"Last screenshot:{Environment.NewLine}{screenshot.ToBase64String()}"); + else + Output.WriteLine("No screenshot available"); + + _automationTestSuite.StopApp(_testKey); + } + + //protected UITestsDeviceBy DeviceBy { get; } + + //protected class UITestsDeviceBy(AppiumDriver driver) + //{ + // public By AutomationId(string id) => + // driver switch + // { + // WindowsDriver => MobileBy.AccessibilityId(id), + // _ => MobileBy.Id(id) + // }; + //} +} diff --git a/DeviceTestingKitApp.UITests.NUnitTests/DeviceTestingKitApp.UITests.NUnitTests.csproj b/sample/test/DeviceTestingKitApp.UITests.NUnitTests/DeviceTestingKitApp.UITests.NUnitTests.csproj similarity index 76% rename from DeviceTestingKitApp.UITests.NUnitTests/DeviceTestingKitApp.UITests.NUnitTests.csproj rename to sample/test/DeviceTestingKitApp.UITests.NUnitTests/DeviceTestingKitApp.UITests.NUnitTests.csproj index af1e4e2..6c295e4 100644 --- a/DeviceTestingKitApp.UITests.NUnitTests/DeviceTestingKitApp.UITests.NUnitTests.csproj +++ b/sample/test/DeviceTestingKitApp.UITests.NUnitTests/DeviceTestingKitApp.UITests.NUnitTests.csproj @@ -18,8 +18,8 @@ - - + + diff --git a/sample/test/DeviceTestingKitApp.UITests.NUnitTests/UITestsSetupFixture.cs b/sample/test/DeviceTestingKitApp.UITests.NUnitTests/UITestsSetupFixture.cs new file mode 100644 index 0000000..3d0eebe --- /dev/null +++ b/sample/test/DeviceTestingKitApp.UITests.NUnitTests/UITestsSetupFixture.cs @@ -0,0 +1,43 @@ +using DeviceRunners.UIAutomation; +using DeviceRunners.UIAutomation.Appium; + +using NUnit.Framework.Internal; + +namespace DeviceTestingKitApp.UITests.NUnitTests; + +[SetUpFixture] +public class UITestsSetupFixture +{ + [OneTimeSetUp] + public void OneTimeSetUp() + { + var builder = AutomationTestSuiteBuilder.Create() + .AddAppium(options => options + .UseServiceAddress("127.0.0.1", 4723) + .AddLogger(new TestContextLogger()) + .AddAndroidApp("android", options => options + .UsePackageName("com.companyname.devicetestingkitapp") + .UseActivityName(".MainActivity")) + .AddWindowsApp("windows", options => options + .UseAppId("com.companyname.devicetestingkitapp_9zz4h110yvjzm!App"))) + ; + //.AddSelenium(options => options + // .AddWebApp("https://dot.net/")); + + TestSuite = builder.Build(); + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + TestSuite.Dispose(); + } + + public static AutomationTestSuite TestSuite { get; private set; } + + class TestContextLogger : IAppiumDiagnosticLogger + { + public void Log(string message) => + TestContext.Out.WriteLine(message); + } +} diff --git a/sample/test/DeviceTestingKitApp.UITests.Windows/DeviceTestingKitApp.UITests.XunitTests.Windows.csproj b/sample/test/DeviceTestingKitApp.UITests.Windows/DeviceTestingKitApp.UITests.XunitTests.Windows.csproj deleted file mode 100644 index 15b0be5..0000000 --- a/sample/test/DeviceTestingKitApp.UITests.Windows/DeviceTestingKitApp.UITests.XunitTests.Windows.csproj +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/sample/test/DeviceTestingKitApp.UITests.XunitTests.Android/DeviceTestingKitApp.UITests.XunitTests.Android.csproj b/sample/test/DeviceTestingKitApp.UITests.XunitTests.Android/DeviceTestingKitApp.UITests.XunitTests.Android.csproj new file mode 100644 index 0000000..d47cc44 --- /dev/null +++ b/sample/test/DeviceTestingKitApp.UITests.XunitTests.Android/DeviceTestingKitApp.UITests.XunitTests.Android.csproj @@ -0,0 +1,5 @@ + + + + + diff --git a/sample/test/DeviceTestingKitApp.UITests.Android/_Config.cs b/sample/test/DeviceTestingKitApp.UITests.XunitTests.Android/_Config.cs similarity index 75% rename from sample/test/DeviceTestingKitApp.UITests.Android/_Config.cs rename to sample/test/DeviceTestingKitApp.UITests.XunitTests.Android/_Config.cs index 693fc31..decb20a 100644 --- a/sample/test/DeviceTestingKitApp.UITests.Android/_Config.cs +++ b/sample/test/DeviceTestingKitApp.UITests.XunitTests.Android/_Config.cs @@ -1,6 +1,6 @@ using System.Runtime.CompilerServices; -namespace DeviceTestingKitApp.UITests; +namespace DeviceTestingKitApp.UITests.XunitTests; public static partial class _Config { diff --git a/sample/test/DeviceTestingKitApp.UITests.XunitTests.Windows/DeviceTestingKitApp.UITests.XunitTests.Windows.csproj b/sample/test/DeviceTestingKitApp.UITests.XunitTests.Windows/DeviceTestingKitApp.UITests.XunitTests.Windows.csproj new file mode 100644 index 0000000..d47cc44 --- /dev/null +++ b/sample/test/DeviceTestingKitApp.UITests.XunitTests.Windows/DeviceTestingKitApp.UITests.XunitTests.Windows.csproj @@ -0,0 +1,5 @@ + + + + + diff --git a/sample/test/DeviceTestingKitApp.UITests.Windows/_Config.cs b/sample/test/DeviceTestingKitApp.UITests.XunitTests.Windows/_Config.cs similarity index 75% rename from sample/test/DeviceTestingKitApp.UITests.Windows/_Config.cs rename to sample/test/DeviceTestingKitApp.UITests.XunitTests.Windows/_Config.cs index be4970e..a578410 100644 --- a/sample/test/DeviceTestingKitApp.UITests.Windows/_Config.cs +++ b/sample/test/DeviceTestingKitApp.UITests.XunitTests.Windows/_Config.cs @@ -1,6 +1,6 @@ using System.Runtime.CompilerServices; -namespace DeviceTestingKitApp.UITests; +namespace DeviceTestingKitApp.UITests.XunitTests; public static partial class _Config { diff --git a/sample/test/DeviceTestingKitApp.UITests/AppiumServerTests.cs b/sample/test/DeviceTestingKitApp.UITests.XunitTests/AppiumServerTests.cs similarity index 66% rename from sample/test/DeviceTestingKitApp.UITests/AppiumServerTests.cs rename to sample/test/DeviceTestingKitApp.UITests.XunitTests/AppiumServerTests.cs index 0f4f5bd..9fde0da 100644 --- a/sample/test/DeviceTestingKitApp.UITests/AppiumServerTests.cs +++ b/sample/test/DeviceTestingKitApp.UITests.XunitTests/AppiumServerTests.cs @@ -1,11 +1,9 @@ using DeviceRunners.UIAutomation.Appium; -using OpenQA.Selenium.Appium.Enums; - using Xunit; using Xunit.Abstractions; -namespace DeviceTestingKitApp.UITests; +namespace DeviceTestingKitApp.UITests.XunitTests; public class AppiumServerTests : BaseUITests { @@ -17,11 +15,12 @@ public AppiumServerTests(UITestsFixture fixture, ITestOutputHelper output) [Fact] public void IsReady() { - var id = Driver.SessionId; + if (App is not AppiumAutomatedApp appiumApp) + return; + + var id = appiumApp.Driver.SessionId; Assert.NotNull(id); Assert.NotEmpty(id.ToString()); - - Assert.Equal(AppState.RunningInForeground, Driver.GetAppState()); } } diff --git a/sample/test/DeviceTestingKitApp.UITests/BaseUITests.cs b/sample/test/DeviceTestingKitApp.UITests.XunitTests/BaseUITests.cs similarity index 58% rename from sample/test/DeviceTestingKitApp.UITests/BaseUITests.cs rename to sample/test/DeviceTestingKitApp.UITests.XunitTests/BaseUITests.cs index 8cb5804..7a4b284 100644 --- a/sample/test/DeviceTestingKitApp.UITests/BaseUITests.cs +++ b/sample/test/DeviceTestingKitApp.UITests.XunitTests/BaseUITests.cs @@ -2,13 +2,12 @@ using System.Xml.Linq; using DeviceRunners.UIAutomation; -using DeviceRunners.UIAutomation.Appium; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; -namespace DeviceTestingKitApp.UITests; +namespace DeviceTestingKitApp.UITests.XunitTests; [Collection(UITestsCollection.CollectionName)] public abstract class BaseUITests : IDisposable @@ -22,8 +21,6 @@ public BaseUITests(UITestsFixture fixture, ITestOutputHelper output) App = _automationTestSuite.StartApp(_Config.Current); Output = output; - DeviceBy = new UITestsDeviceBy(Driver); - _xunitTest = GetTest(Output as TestOutputHelper); } @@ -33,37 +30,19 @@ public BaseUITests(UITestsFixture fixture, ITestOutputHelper output) public void Dispose() { - Output.WriteLine("Last page source:"); - if (App.GetPageSource() is string source && !string.IsNullOrWhiteSpace(source)) - Output.WriteLine(XDocument.Parse(source).ToString()); + if (App?.GetPageSource() is string source && !string.IsNullOrWhiteSpace(source)) + Output.WriteLine($"Last page source:{Environment.NewLine}{XDocument.Parse(source)}"); else Output.WriteLine("Page source is empty"); - Output.WriteLine("Last screenshot:"); - if (App.GetScreenshot() is { } screenshot) - { - Output.WriteLine(screenshot.ToBase64String()); - } + if (App?.GetScreenshot() is { } screenshot) + Output.WriteLine($"Last screenshot:{Environment.NewLine}{screenshot.ToBase64String()}"); else - { Output.WriteLine("No screenshot available"); - } _automationTestSuite.StopApp(_Config.Current); } - protected UITestsDeviceBy DeviceBy { get; } - - protected class UITestsDeviceBy(AppiumDriver driver) - { - public By AutomationId(string id) => - driver switch - { - WindowsDriver => MobileBy.AccessibilityId(id), - _ => MobileBy.Id(id) - }; - } - [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "test")] static extern ref ITest GetTest(TestOutputHelper? output); } diff --git a/sample/test/DeviceTestingKitApp.UITests/DeviceTestingKitApp.UITests.XunitTests.csproj b/sample/test/DeviceTestingKitApp.UITests.XunitTests/DeviceTestingKitApp.UITests.XunitTests.csproj similarity index 100% rename from sample/test/DeviceTestingKitApp.UITests/DeviceTestingKitApp.UITests.XunitTests.csproj rename to sample/test/DeviceTestingKitApp.UITests.XunitTests/DeviceTestingKitApp.UITests.XunitTests.csproj diff --git a/sample/test/DeviceTestingKitApp.UITests/DeviceTestingKitApp.UITests.targets b/sample/test/DeviceTestingKitApp.UITests.XunitTests/DeviceTestingKitApp.UITests.XunitTests.targets similarity index 93% rename from sample/test/DeviceTestingKitApp.UITests/DeviceTestingKitApp.UITests.targets rename to sample/test/DeviceTestingKitApp.UITests.XunitTests/DeviceTestingKitApp.UITests.XunitTests.targets index 90dc854..d9d4761 100644 --- a/sample/test/DeviceTestingKitApp.UITests/DeviceTestingKitApp.UITests.targets +++ b/sample/test/DeviceTestingKitApp.UITests.XunitTests/DeviceTestingKitApp.UITests.XunitTests.targets @@ -7,7 +7,7 @@ false true true - DeviceTestingKitApp.UITests + DeviceTestingKitApp.UITests.XunitTests diff --git a/sample/test/DeviceTestingKitApp.UITests/MainPageTests.cs b/sample/test/DeviceTestingKitApp.UITests.XunitTests/MainPageTests.cs similarity index 60% rename from sample/test/DeviceTestingKitApp.UITests/MainPageTests.cs rename to sample/test/DeviceTestingKitApp.UITests.XunitTests/MainPageTests.cs index 6279199..46c684f 100644 --- a/sample/test/DeviceTestingKitApp.UITests/MainPageTests.cs +++ b/sample/test/DeviceTestingKitApp.UITests.XunitTests/MainPageTests.cs @@ -1,7 +1,9 @@ -using Xunit; +using DeviceRunners.UIAutomation; + +using Xunit; using Xunit.Abstractions; -namespace DeviceTestingKitApp.UITests; +namespace DeviceTestingKitApp.UITests.XunitTests; public class MainPageTests : BaseUITests { @@ -13,20 +15,20 @@ public MainPageTests(UITestsFixture fixture, ITestOutputHelper output) [Fact] public void InitialStateIsCorrect() { - var element = Driver.FindElement(DeviceBy.AutomationId("CounterButton")); + var element = App.FindElement(by => by.Id("CounterButton")); - Assert.Equal("Click me!", element.Text); + Assert.Equal("Click me!", element.GetText()); } [Fact] public async Task SingleIncrementIncrementsByOne() { - var element = Driver.FindElement(DeviceBy.AutomationId("CounterButton")); + var element = App.FindElement(by => by.Id("CounterButton")); element.Click(); await Task.Delay(500); - Assert.Equal("Clicked 1 time", element.Text); + Assert.Equal("Clicked 1 time", element.GetText()); } [Theory] @@ -36,7 +38,7 @@ public async Task SingleIncrementIncrementsByOne() [InlineData(3, "Clicked 3 times")] public async Task ClickingMultipleTimesKeepsIncrementing(int clicks, string text) { - var element = Driver.FindElement(DeviceBy.AutomationId("CounterButton")); + var element = App.FindElement(by => by.Id("CounterButton")); for (var i = 0; i < clicks; i++) { @@ -44,6 +46,6 @@ public async Task ClickingMultipleTimesKeepsIncrementing(int clicks, string text await Task.Delay(500); } - Assert.Equal(text, element.Text); + Assert.Equal(text, element.GetText()); } } diff --git a/sample/test/DeviceTestingKitApp.UITests/UITestsFixture.cs b/sample/test/DeviceTestingKitApp.UITests.XunitTests/UITestsFixture.cs similarity index 96% rename from sample/test/DeviceTestingKitApp.UITests/UITestsFixture.cs rename to sample/test/DeviceTestingKitApp.UITests.XunitTests/UITestsFixture.cs index dfb002b..7970508 100644 --- a/sample/test/DeviceTestingKitApp.UITests/UITestsFixture.cs +++ b/sample/test/DeviceTestingKitApp.UITests.XunitTests/UITestsFixture.cs @@ -5,7 +5,7 @@ using Xunit.Abstractions; using Xunit.Sdk; -namespace DeviceTestingKitApp.UITests; +namespace DeviceTestingKitApp.UITests.XunitTests; public class UITestsFixture : IDisposable { diff --git a/sample/test/DeviceTestingKitApp.UITests/_Config.Common.cs b/sample/test/DeviceTestingKitApp.UITests.XunitTests/_Config.Common.cs similarity index 90% rename from sample/test/DeviceTestingKitApp.UITests/_Config.Common.cs rename to sample/test/DeviceTestingKitApp.UITests.XunitTests/_Config.Common.cs index a7715dd..99c9327 100644 --- a/sample/test/DeviceTestingKitApp.UITests/_Config.Common.cs +++ b/sample/test/DeviceTestingKitApp.UITests.XunitTests/_Config.Common.cs @@ -1,4 +1,4 @@ -namespace DeviceTestingKitApp.UITests; +namespace DeviceTestingKitApp.UITests.XunitTests; /// /// xUnit does not support parameterized tests yet, so we have to use multiple diff --git a/sample/test/DeviceTestingKitApp.UITests/xunit.runner.json b/sample/test/DeviceTestingKitApp.UITests.XunitTests/xunit.runner.json similarity index 100% rename from sample/test/DeviceTestingKitApp.UITests/xunit.runner.json rename to sample/test/DeviceTestingKitApp.UITests.XunitTests/xunit.runner.json diff --git a/src/DeviceRunners.UIAutomation.Appium/AppiumAutomatedApp.cs b/src/DeviceRunners.UIAutomation.Appium/AppiumAutomatedApp.cs index 0fa95ae..8c591b7 100644 --- a/src/DeviceRunners.UIAutomation.Appium/AppiumAutomatedApp.cs +++ b/src/DeviceRunners.UIAutomation.Appium/AppiumAutomatedApp.cs @@ -9,50 +9,53 @@ public class AppiumAutomatedApp : IAutomatedApp { private readonly AppiumAutomatedAppOptions _options; private readonly IAppiumDiagnosticLogger? _logger; - private readonly AppiumDriverManager _driverManager; - - private bool _disposed; public AppiumAutomatedApp(AppiumAutomationFramework appium, AppiumAutomatedAppOptions options, IAppiumDiagnosticLogger? logger = null) { Framework = appium; _options = options; _logger = logger; - _driverManager = new AppiumDriverManager(Framework.ServiceManager, _options); - Commands = new AutomatedAppCommandExecutor(this); + DriverManager = new AppiumDriverManager(Framework.ServiceManager, _options); + Commands = new AutomatedAppCommandManager(this, options.Commands); } public AppiumAutomationFramework Framework { get; } - public AppiumServiceManager ServiceManager - { - get - { - ObjectDisposedException.ThrowIf(_disposed, typeof(AppiumAutomatedApp)); - return Framework.ServiceManager; - } - } + public AppiumServiceManager ServiceManager => Framework.ServiceManager; - public AppiumDriverManager DriverManager - { - get - { - ObjectDisposedException.ThrowIf(_disposed, typeof(AppiumAutomatedApp)); - return _driverManager; - } - } + public AppiumDriverManager DriverManager { get; } public AppiumDriver Driver => DriverManager.Driver; public IAutomatedAppCommandManager Commands { get; } - public void Dispose() + public AppiumAutomatedAppElement FindElement(Action by) + { + ArgumentNullException.ThrowIfNull(by); + + var appiumBy = _options.ByFactory.Create(this); + by(appiumBy); + + var element = Driver.FindElement(appiumBy.ToBy()); + + return new AppiumAutomatedAppElement(this, element); + } + + IAutomatedAppElement IContainsElements.FindElement(Action by) => + FindElement(by); + + public IReadOnlyList FindElements(Action by) { - if (_disposed) - return; + ArgumentNullException.ThrowIfNull(by); + + var appiumBy = _options.ByFactory.Create(this); + by(appiumBy); - _disposed = true; + var elements = Driver.FindElements(appiumBy.ToBy()); - _driverManager.Dispose(); + return elements.Select(e => new AppiumAutomatedAppElement(this, e)).ToList(); } + + IReadOnlyList IContainsElements.FindElements(Action by) => + FindElements(by); } diff --git a/src/DeviceRunners.UIAutomation.Appium/AppiumAutomatedAppElement.cs b/src/DeviceRunners.UIAutomation.Appium/AppiumAutomatedAppElement.cs new file mode 100644 index 0000000..496eb13 --- /dev/null +++ b/src/DeviceRunners.UIAutomation.Appium/AppiumAutomatedAppElement.cs @@ -0,0 +1,18 @@ +using OpenQA.Selenium.Appium; + +namespace DeviceRunners.UIAutomation.Appium; + +public class AppiumAutomatedAppElement : IAutomatedAppElement +{ + public AppiumAutomatedAppElement(AppiumAutomatedApp app, AppiumElement element) + { + App = app; + AppiumElement = element; + } + + public AppiumAutomatedApp App { get; } + + public AppiumElement AppiumElement { get; } + + IAutomatedApp IAutomatedAppElement.App => App; +} diff --git a/src/DeviceRunners.UIAutomation.Appium/AppiumAutomatedAppOptions.cs b/src/DeviceRunners.UIAutomation.Appium/AppiumAutomatedAppOptions.cs index db46b4f..0576639 100644 --- a/src/DeviceRunners.UIAutomation.Appium/AppiumAutomatedAppOptions.cs +++ b/src/DeviceRunners.UIAutomation.Appium/AppiumAutomatedAppOptions.cs @@ -4,20 +4,27 @@ namespace DeviceRunners.UIAutomation.Appium; public abstract class AppiumAutomatedAppOptions : IAutomatedAppOptions { - public AppiumAutomatedAppOptions(string key, IAppiumDriverFactory driverFactory, AppiumOptions appiumOptions) + public AppiumAutomatedAppOptions(string key, IAppiumDriverFactory driverFactory, IAppiumByFactory byFactory, AppiumOptions appiumOptions, IReadOnlyList commands) { ArgumentException.ThrowIfNullOrWhiteSpace(key, nameof(key)); ArgumentNullException.ThrowIfNull(driverFactory, nameof(driverFactory)); + ArgumentNullException.ThrowIfNull(byFactory, nameof(byFactory)); ArgumentNullException.ThrowIfNull(appiumOptions, nameof(appiumOptions)); Key = key; DriverFactory = driverFactory; + ByFactory = byFactory; AppiumOptions = appiumOptions; + Commands = commands; } public string Key { get; } public IAppiumDriverFactory DriverFactory { get; } + + public IAppiumByFactory ByFactory { get; } public AppiumOptions AppiumOptions { get; } + + public IReadOnlyList Commands { get; } } diff --git a/src/DeviceRunners.UIAutomation.Appium/AppiumAutomatedAppOptionsBuilder.cs b/src/DeviceRunners.UIAutomation.Appium/AppiumAutomatedAppOptionsBuilder.cs index 9da0a67..a3e095b 100644 --- a/src/DeviceRunners.UIAutomation.Appium/AppiumAutomatedAppOptionsBuilder.cs +++ b/src/DeviceRunners.UIAutomation.Appium/AppiumAutomatedAppOptionsBuilder.cs @@ -4,7 +4,7 @@ namespace DeviceRunners.UIAutomation.Appium; public abstract class AppiumAutomatedAppOptionsBuilder : IAutomatedAppOptionsBuilder { - private readonly Stack _commands = new(); + private readonly List _commands = []; public AppiumAutomatedAppOptionsBuilder(string key) { @@ -16,8 +16,10 @@ public AppiumAutomatedAppOptionsBuilder(string key) public AppiumOptions AppiumOptions { get; } = new(); + public IReadOnlyList Commands => _commands; + void IAutomatedAppOptionsBuilder.AddCommand(IAutomatedAppCommand command) => - _commands.Push(command); + _commands.Add(command); public abstract AppiumAutomatedAppOptions Build(); diff --git a/src/DeviceRunners.UIAutomation.Appium/AppiumBy.cs b/src/DeviceRunners.UIAutomation.Appium/AppiumBy.cs new file mode 100644 index 0000000..b8b3915 --- /dev/null +++ b/src/DeviceRunners.UIAutomation.Appium/AppiumBy.cs @@ -0,0 +1,32 @@ + +using OpenQA.Selenium; +using OpenQA.Selenium.Appium; + +namespace DeviceRunners.UIAutomation.Appium; + +public class AppiumBy : IBy +{ + private By? _by; + + void IBy.Selector(string selector, string value) + { + if (!SetBy(selector, value)) + throw new ArgumentException($"Unknown element selector '{selector}' with value '{value}'."); + } + + protected bool SetBy(By by) + { + _by = by; + return true; + } + + protected virtual bool SetBy(string selector, string value) => + selector switch + { + BySelectors.Id => SetBy(MobileBy.Id(value)), + BySelectors.AccessibilityId => SetBy(MobileBy.AccessibilityId(value)), + _ => false + }; + + public By ToBy() => _by ?? throw new InvalidOperationException("No element selector was specified."); +} diff --git a/src/DeviceRunners.UIAutomation.Appium/AppiumByFactory.cs b/src/DeviceRunners.UIAutomation.Appium/AppiumByFactory.cs new file mode 100644 index 0000000..de500e9 --- /dev/null +++ b/src/DeviceRunners.UIAutomation.Appium/AppiumByFactory.cs @@ -0,0 +1,6 @@ +namespace DeviceRunners.UIAutomation.Appium; + +public class AppiumByFactory : IAppiumByFactory +{ + public virtual AppiumBy Create(AppiumAutomatedApp app) => new AppiumBy(); +} diff --git a/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Android/AndroidAppiumAutomatedAppOptions.cs b/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Android/AndroidAppiumAutomatedAppOptions.cs index 8e08043..201f2f0 100644 --- a/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Android/AndroidAppiumAutomatedAppOptions.cs +++ b/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Android/AndroidAppiumAutomatedAppOptions.cs @@ -4,8 +4,8 @@ namespace DeviceRunners.UIAutomation.Appium; public class AndroidAppiumAutomatedAppOptions : AppiumAutomatedAppOptions { - public AndroidAppiumAutomatedAppOptions(string key, IAppiumDriverFactory driverFactory, AppiumOptions appiumOptions) - : base(key, driverFactory, appiumOptions) + public AndroidAppiumAutomatedAppOptions(string key, AppiumOptions appiumOptions, IReadOnlyList commands) + : base(key, new AndroidAppiumDriverFactory(), new AppiumByFactory(), appiumOptions, commands) { } } diff --git a/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Android/AndroidAppiumAutomatedAppOptionsBuilder.cs b/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Android/AndroidAppiumAutomatedAppOptionsBuilder.cs index 08b6828..9b5b76f 100644 --- a/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Android/AndroidAppiumAutomatedAppOptionsBuilder.cs +++ b/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Android/AndroidAppiumAutomatedAppOptionsBuilder.cs @@ -31,5 +31,5 @@ public AndroidAppiumAutomatedAppOptionsBuilder UseAppPackagePath(string path) } public override AppiumAutomatedAppOptions Build() => - new AndroidAppiumAutomatedAppOptions(Key, new AndroidAppiumDriverFactory(), AppiumOptions); + new AndroidAppiumAutomatedAppOptions(Key, AppiumOptions, Commands); } diff --git a/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Windows/WindowsAppiumAutomatedAppOptions.cs b/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Windows/WindowsAppiumAutomatedAppOptions.cs index 5791f0a..0c533e1 100644 --- a/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Windows/WindowsAppiumAutomatedAppOptions.cs +++ b/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Windows/WindowsAppiumAutomatedAppOptions.cs @@ -4,8 +4,8 @@ namespace DeviceRunners.UIAutomation.Appium; public class WindowsAppiumAutomatedAppOptions : AppiumAutomatedAppOptions { - public WindowsAppiumAutomatedAppOptions(string key, IAppiumDriverFactory driverFactory, AppiumOptions appiumOptions) - : base(key, driverFactory, appiumOptions) + public WindowsAppiumAutomatedAppOptions(string key, AppiumOptions appiumOptions, IReadOnlyList commands) + : base(key, new WindowsAppiumDriverFactory(), new WindowsAppiumByFactory(), appiumOptions, commands) { } } diff --git a/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Windows/WindowsAppiumAutomatedAppOptionsBuilder.cs b/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Windows/WindowsAppiumAutomatedAppOptionsBuilder.cs index c139895..f16dd81 100644 --- a/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Windows/WindowsAppiumAutomatedAppOptionsBuilder.cs +++ b/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Windows/WindowsAppiumAutomatedAppOptionsBuilder.cs @@ -25,5 +25,5 @@ public WindowsAppiumAutomatedAppOptionsBuilder UseExecutablePath(string executab } public override AppiumAutomatedAppOptions Build() => - new WindowsAppiumAutomatedAppOptions(Key, new WindowsAppiumDriverFactory(), AppiumOptions); + new WindowsAppiumAutomatedAppOptions(Key, AppiumOptions, Commands); } diff --git a/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Windows/WindowsAppiumBy.cs b/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Windows/WindowsAppiumBy.cs new file mode 100644 index 0000000..a3c1114 --- /dev/null +++ b/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Windows/WindowsAppiumBy.cs @@ -0,0 +1,13 @@ +namespace DeviceRunners.UIAutomation.Appium; + +public class WindowsAppiumBy : AppiumBy +{ + protected override bool SetBy(string selector, string value) + { + // Windows does not use ID but rather Accessibility ID to find elements + if (selector == BySelectors.Id) + selector = BySelectors.AccessibilityId; + + return base.SetBy(selector, value); + } +} diff --git a/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Windows/WindowsAppiumByFactory.cs b/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Windows/WindowsAppiumByFactory.cs new file mode 100644 index 0000000..d23bd81 --- /dev/null +++ b/src/DeviceRunners.UIAutomation.Appium/AutomatedPlatforms/Windows/WindowsAppiumByFactory.cs @@ -0,0 +1,6 @@ +namespace DeviceRunners.UIAutomation.Appium; + +public class WindowsAppiumByFactory : AppiumByFactory +{ + public override AppiumBy Create(AppiumAutomatedApp app) => new WindowsAppiumBy(); +} diff --git a/src/DeviceRunners.UIAutomation.Appium/Commands/Android/AndroidAppiumDismissKeyboardCommand.cs b/src/DeviceRunners.UIAutomation.Appium/Commands/Android/AndroidAppiumDismissKeyboardCommand.cs index c519c30..1c1f1ba 100644 --- a/src/DeviceRunners.UIAutomation.Appium/Commands/Android/AndroidAppiumDismissKeyboardCommand.cs +++ b/src/DeviceRunners.UIAutomation.Appium/Commands/Android/AndroidAppiumDismissKeyboardCommand.cs @@ -3,7 +3,7 @@ public class AndroidAppiumDismissKeyboardCommand : AutomatedAppCommand { public AndroidAppiumDismissKeyboardCommand() - : base(AppiumCommandNames.DismissKeyboard) + : base(AppiumCommonCommandNames.DismissKeyboard) { } diff --git a/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumAutomatedAppOptionsBuilderExtensions.cs b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumAutomatedAppOptionsBuilderExtensions.cs index d3d4828..fa56989 100644 --- a/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumAutomatedAppOptionsBuilderExtensions.cs +++ b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumAutomatedAppOptionsBuilderExtensions.cs @@ -7,6 +7,9 @@ public static TBuilder AddDefaultAppiumCommands(this TBuilder builder) { builder.AddCommand(new AppiumGetPageSourceCommand()); builder.AddCommand(new AppiumGetScreenshotCommand()); + builder.AddCommand(new AppiumGetElementTextCommand()); + builder.AddCommand(new AppiumClickElementCommand()); + builder.AddCommand(new AppiumClickCoordinatesCommand()); return builder; } diff --git a/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumClickCoordinatesCommand.cs b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumClickCoordinatesCommand.cs new file mode 100644 index 0000000..6e5b3d9 --- /dev/null +++ b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumClickCoordinatesCommand.cs @@ -0,0 +1,32 @@ +using OpenQA.Selenium.Appium; +using OpenQA.Selenium.Appium.Interactions; +using OpenQA.Selenium.Interactions; + +using PointerInputDevice = OpenQA.Selenium.Appium.Interactions.PointerInputDevice; + +namespace DeviceRunners.UIAutomation.Appium; + +public class AppiumClickCoordinatesCommand : AppiumElementCommand +{ + public AppiumClickCoordinatesCommand() + : base(CommonCommandNames.ClickCoordinates) + { + } + + public override object? Execute(AppiumAutomatedApp app, AppiumElement appiumElement, IReadOnlyDictionary parameters) + { + if (!parameters.TryGetValue("x", out var x)) + throw new ArgumentException("X coordinate not found in parameters", nameof(parameters)); + if (!parameters.TryGetValue("y", out var y)) + throw new ArgumentException("Y coordinate not found in parameters", nameof(parameters)); + + var touchDevice = new PointerInputDevice(PointerKind.Mouse); + var sequence = new ActionSequence(touchDevice, 0); + sequence.AddAction(touchDevice.CreatePointerMove(CoordinateOrigin.Viewport, Convert.ToInt32(x), Convert.ToInt32(y), TimeSpan.FromMilliseconds(5))); + sequence.AddAction(touchDevice.CreatePointerDown(PointerButton.TouchContact)); + sequence.AddAction(touchDevice.CreatePointerUp(PointerButton.TouchContact)); + app.Driver.PerformActions([sequence]); + + return null; + } +} diff --git a/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumClickElementCommand.cs b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumClickElementCommand.cs new file mode 100644 index 0000000..18bc3db --- /dev/null +++ b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumClickElementCommand.cs @@ -0,0 +1,34 @@ +using OpenQA.Selenium; +using OpenQA.Selenium.Appium; + +namespace DeviceRunners.UIAutomation.Appium; + +public class AppiumClickElementCommand : AppiumElementCommand +{ + public AppiumClickElementCommand() + : base(CommonCommandNames.ClickElement) + { + } + + public override object? Execute(AppiumAutomatedApp app, AppiumElement appiumElement, IReadOnlyDictionary parameters) + { + try + { + appiumElement.Click(); + return null; + } + catch (Exception ex) when (ex is WebDriverException || ex is InvalidOperationException) + { + // Some elements aren't "clickable" from an automation perspective, such + // as borders and labels. In this case, we can try to tap the element using + // its center point - which is what the click does anyway. + + var pointString = appiumElement.GetAttribute("ClickablePoint"); + var parts = pointString.Split(','); + double x = double.Parse(parts[0]); + double y = double.Parse(parts[1]); + + return app.Commands.Execute(CommonCommandNames.ClickCoordinates, ("x", x), ("y", y)); + } + } +} diff --git a/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumCommandNames.cs b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumCommandNames.cs deleted file mode 100644 index f29d019..0000000 --- a/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumCommandNames.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace DeviceRunners.UIAutomation.Appium; - -public static class AppiumCommandNames -{ - public const string GetPageSource = "getPageSource"; - public const string GetScreenshot = "getScreenshot"; - public const string DismissKeyboard = "dismissKeyboard"; -} diff --git a/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumCommonCommandNames.cs b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumCommonCommandNames.cs new file mode 100644 index 0000000..8d3fb9b --- /dev/null +++ b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumCommonCommandNames.cs @@ -0,0 +1,6 @@ +namespace DeviceRunners.UIAutomation.Appium; + +public static class AppiumCommonCommandNames +{ + public const string DismissKeyboard = "dismissKeyboard"; +} diff --git a/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumElementCommand .cs b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumElementCommand .cs new file mode 100644 index 0000000..fb19db5 --- /dev/null +++ b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumElementCommand .cs @@ -0,0 +1,24 @@ +using OpenQA.Selenium.Appium; + +namespace DeviceRunners.UIAutomation.Appium; + +public abstract class AppiumElementCommand : AutomatedAppCommand +{ + protected AppiumElementCommand(string name) + : base(name) + { + } + + public abstract object? Execute(AppiumAutomatedApp app, AppiumElement appiumElement, IReadOnlyDictionary parameters); + + public override object? Execute(AppiumAutomatedApp app, IReadOnlyDictionary? parameters = null) + { + if (parameters is null || !parameters.TryGetValue("element", out var element)) + throw new ArgumentException("Element not found in parameters", nameof(parameters)); + + if (element is not AppiumAutomatedAppElement appiumElement) + throw new ArgumentException("Element is not an Appium element", nameof(parameters)); + + return Execute(app, appiumElement.AppiumElement, parameters); + } +} diff --git a/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumGetElementTextCommand.cs b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumGetElementTextCommand.cs new file mode 100644 index 0000000..09255e9 --- /dev/null +++ b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumGetElementTextCommand.cs @@ -0,0 +1,14 @@ +using OpenQA.Selenium.Appium; + +namespace DeviceRunners.UIAutomation.Appium; + +public class AppiumGetElementTextCommand : AppiumElementCommand +{ + public AppiumGetElementTextCommand() + : base(CommonCommandNames.GetElementText) + { + } + + public override object? Execute(AppiumAutomatedApp app, AppiumElement appiumElement, IReadOnlyDictionary parameters) => + appiumElement.Text; +} diff --git a/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumGetPageSourceCommand.cs b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumGetPageSourceCommand.cs index 00d9828..85324e5 100644 --- a/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumGetPageSourceCommand.cs +++ b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumGetPageSourceCommand.cs @@ -3,7 +3,7 @@ public class AppiumGetPageSourceCommand : AutomatedAppCommand { public AppiumGetPageSourceCommand() - : base(AppiumCommandNames.GetPageSource) + : base(CommonCommandNames.GetPageSource) { } diff --git a/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumGetScreenshotCommand.cs b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumGetScreenshotCommand.cs index 13b9348..eac6cec 100644 --- a/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumGetScreenshotCommand.cs +++ b/src/DeviceRunners.UIAutomation.Appium/Commands/AppiumGetScreenshotCommand.cs @@ -3,7 +3,7 @@ public class AppiumGetScreenshotCommand : AutomatedAppCommand { public AppiumGetScreenshotCommand() - : base(AppiumCommandNames.GetScreenshot) + : base(CommonCommandNames.GetScreenshot) { } diff --git a/src/DeviceRunners.UIAutomation.Appium/Commands/AutomatedAppExtensions.cs b/src/DeviceRunners.UIAutomation.Appium/Commands/AutomatedAppExtensions.cs index c94d228..895af27 100644 --- a/src/DeviceRunners.UIAutomation.Appium/Commands/AutomatedAppExtensions.cs +++ b/src/DeviceRunners.UIAutomation.Appium/Commands/AutomatedAppExtensions.cs @@ -2,12 +2,6 @@ public static class AutomatedAppExtensions { - public static string? GetPageSource(this IAutomatedApp app) => - app.Commands.Execute(AppiumCommandNames.GetPageSource) as string; - - public static IInMemoryFile? GetScreenshot(this IAutomatedApp app) => - app.Commands.Execute(AppiumCommandNames.GetScreenshot) as IInMemoryFile; - public static void DismissKeyboard(this IAutomatedApp app) => - app.Commands.Execute(AppiumCommandNames.DismissKeyboard); + app.Commands.Execute(CommonCommandNames.DismissKeyboard); } diff --git a/src/DeviceRunners.UIAutomation.Appium/DriverManager/AppiumDriverManager.cs b/src/DeviceRunners.UIAutomation.Appium/DriverManager/AppiumDriverManager.cs index 7b22825..9e9ed01 100644 --- a/src/DeviceRunners.UIAutomation.Appium/DriverManager/AppiumDriverManager.cs +++ b/src/DeviceRunners.UIAutomation.Appium/DriverManager/AppiumDriverManager.cs @@ -38,7 +38,7 @@ public void ShutdownDriver() _driver.Dispose(); var delta = TimeSpan.FromMilliseconds(Environment.TickCount - ticks).TotalSeconds; - _logger?.Log($"Driver restarted in {delta} seconds."); + _logger?.Log($"Driver shut down in {delta} seconds."); } public void RestartDriver() diff --git a/src/DeviceRunners.UIAutomation.Appium/IAppiumByFactory.cs b/src/DeviceRunners.UIAutomation.Appium/IAppiumByFactory.cs new file mode 100644 index 0000000..322c72a --- /dev/null +++ b/src/DeviceRunners.UIAutomation.Appium/IAppiumByFactory.cs @@ -0,0 +1,6 @@ +namespace DeviceRunners.UIAutomation.Appium; + +public interface IAppiumByFactory +{ + AppiumBy Create(AppiumAutomatedApp app); +} diff --git a/src/DeviceRunners.UIAutomation/AutomationTestSuite.cs b/src/DeviceRunners.UIAutomation/AutomationTestSuite.cs index a9a2db8..1954c17 100644 --- a/src/DeviceRunners.UIAutomation/AutomationTestSuite.cs +++ b/src/DeviceRunners.UIAutomation/AutomationTestSuite.cs @@ -6,6 +6,7 @@ public class AutomationTestSuite : IDisposable { private readonly IReadOnlyList _automationFrameworks; private readonly IReadOnlyDictionary _availableApps; + private readonly IReadOnlyList _availableAppKeys; private readonly ConcurrentDictionary _instantiatedApps = new(); private bool _disposed; @@ -17,6 +18,7 @@ public AutomationTestSuite(IReadOnlyList automationFramewo { _automationFrameworks = automationFrameworks; _availableApps = CollectAvailableApps(); + _availableAppKeys = _availableApps.Keys.ToList(); } public IAutomatedApp GetApp(string appKey) => @@ -27,24 +29,33 @@ public IAutomatedApp StartApp(string appKey) => public void StopApp(string appKey) { - if (_instantiatedApps.TryGetValue(appKey, out var instantiatedApp)) + if (_instantiatedApps.TryRemove(appKey, out var instantiatedApp)) instantiatedApp.Framework.StopApp(instantiatedApp.App); } public IAutomatedApp RestartApp(string appKey) => GetApp(appKey, true, true); + public IReadOnlyCollection Frameworks => _automationFrameworks; + + public IReadOnlyCollection AvailableApps => _availableAppKeys; + + public IReadOnlyCollection InstantiatedApps => [.. _instantiatedApps.Keys]; + private IAutomatedApp GetApp(string appKey, bool start, bool restart) { ObjectDisposedException.ThrowIf(_disposed, typeof(AutomationTestSuite)); + if (!_availableAppKeys.Contains(appKey)) + throw new KeyNotFoundException($"App with key '{appKey}' was not found."); + var instantiatedApp = _instantiatedApps.AddOrUpdate( appKey, _ => { if (!_availableApps.TryGetValue(appKey, out var appOptions)) throw new KeyNotFoundException($"App with key '{appKey}' was not found."); - + var framework = appOptions.Framework; var app = framework.CreateApp(appOptions.Options); @@ -60,7 +71,7 @@ private IAutomatedApp GetApp(string appKey, bool start, bool restart) if (restart) framework.RestartApp(app); - + return instantiated; }); @@ -74,10 +85,10 @@ public void Dispose() _disposed = true; - // close all apps + // stop all apps foreach (var app in _instantiatedApps.Values) { - app.App.Dispose(); + app.Framework.StopApp(app.App); } _instantiatedApps.Clear(); diff --git a/src/DeviceRunners.UIAutomation/AutomationTestSuiteBuilder.cs b/src/DeviceRunners.UIAutomation/AutomationTestSuiteBuilder.cs index 90b49b8..e1c4ee4 100644 --- a/src/DeviceRunners.UIAutomation/AutomationTestSuiteBuilder.cs +++ b/src/DeviceRunners.UIAutomation/AutomationTestSuiteBuilder.cs @@ -4,6 +4,10 @@ public class AutomationTestSuiteBuilder { private readonly List _automationFrameworks = []; + private AutomationTestSuiteBuilder() + { + } + public static AutomationTestSuiteBuilder Create() => new(); public AutomationTestSuiteBuilder AddAutomationFramework(IAutomationFramework framework) @@ -21,7 +25,7 @@ public AutomationTestSuite Build() { foreach (var app in framework.AvailableApps) { - if (apps.Add(app.Key)) + if (!apps.Add(app.Key)) { throw new InvalidOperationException($"App with key '{app.Key}' is registered multiple times."); } diff --git a/src/DeviceRunners.UIAutomation/ByExtensions.cs b/src/DeviceRunners.UIAutomation/ByExtensions.cs new file mode 100644 index 0000000..71a0ed5 --- /dev/null +++ b/src/DeviceRunners.UIAutomation/ByExtensions.cs @@ -0,0 +1,8 @@ +namespace DeviceRunners.UIAutomation; + +public static class ByExtensions +{ + public static void Id(this IBy by, string id) => by.Selector(BySelectors.Id, id); + + public static void AccessibilityId(this IBy by, string id) => by.Selector(BySelectors.AccessibilityId, id); +} diff --git a/src/DeviceRunners.UIAutomation/BySelectors.cs b/src/DeviceRunners.UIAutomation/BySelectors.cs new file mode 100644 index 0000000..4a3eb8c --- /dev/null +++ b/src/DeviceRunners.UIAutomation/BySelectors.cs @@ -0,0 +1,7 @@ +namespace DeviceRunners.UIAutomation; + +public static class BySelectors +{ + public const string Id = "Id"; + public const string AccessibilityId = "AccessibilityId"; +} diff --git a/src/DeviceRunners.UIAutomation/Commands/AutomatedAppCommandExecutor.cs b/src/DeviceRunners.UIAutomation/Commands/AutomatedAppCommandManager.cs similarity index 70% rename from src/DeviceRunners.UIAutomation/Commands/AutomatedAppCommandExecutor.cs rename to src/DeviceRunners.UIAutomation/Commands/AutomatedAppCommandManager.cs index f6297b8..a3ecc6b 100644 --- a/src/DeviceRunners.UIAutomation/Commands/AutomatedAppCommandExecutor.cs +++ b/src/DeviceRunners.UIAutomation/Commands/AutomatedAppCommandManager.cs @@ -1,12 +1,13 @@ namespace DeviceRunners.UIAutomation; -public class AutomatedAppCommandExecutor : IAutomatedAppCommandManager +public class AutomatedAppCommandManager : IAutomatedAppCommandManager { - private readonly Stack _commands = new(); + private readonly IReadOnlyList _commands; - public AutomatedAppCommandExecutor(IAutomatedApp app) + public AutomatedAppCommandManager(IAutomatedApp app, IReadOnlyList commands) { App = app; + _commands = commands; } public IAutomatedApp App { get; } diff --git a/src/DeviceRunners.UIAutomation/Commands/AutomatedAppCommandManagerExtensions.cs b/src/DeviceRunners.UIAutomation/Commands/AutomatedAppCommandManagerExtensions.cs new file mode 100644 index 0000000..f2a2519 --- /dev/null +++ b/src/DeviceRunners.UIAutomation/Commands/AutomatedAppCommandManagerExtensions.cs @@ -0,0 +1,7 @@ +namespace DeviceRunners.UIAutomation; + +public static class AutomatedAppCommandManagerExtensions +{ + public static object? Execute(this IAutomatedAppCommandManager manager, string commandName, params (string Name, object Value)[] parameters) => + manager.Execute(commandName, parameters.ToDictionary()); +} diff --git a/src/DeviceRunners.UIAutomation/Commands/AutomatedAppElementExtensions.cs b/src/DeviceRunners.UIAutomation/Commands/AutomatedAppElementExtensions.cs new file mode 100644 index 0000000..02bcd9a --- /dev/null +++ b/src/DeviceRunners.UIAutomation/Commands/AutomatedAppElementExtensions.cs @@ -0,0 +1,13 @@ +namespace DeviceRunners.UIAutomation; + +public static class AutomatedAppElementExtensions +{ + public static string? GetText(this IAutomatedAppElement element) => + element.ExecuteCommand(CommonCommandNames.GetElementText) as string; + + public static void Click(this IAutomatedAppElement element) => + element.ExecuteCommand(CommonCommandNames.ClickElement); + + private static object? ExecuteCommand(this IAutomatedAppElement element, string commandName) => + element.App.Commands.Execute(commandName, ("element", element)); +} diff --git a/src/DeviceRunners.UIAutomation/Commands/AutomatedAppExtensions.cs b/src/DeviceRunners.UIAutomation/Commands/AutomatedAppExtensions.cs new file mode 100644 index 0000000..ecf0398 --- /dev/null +++ b/src/DeviceRunners.UIAutomation/Commands/AutomatedAppExtensions.cs @@ -0,0 +1,16 @@ +namespace DeviceRunners.UIAutomation; + +public static class AutomatedAppExtensions +{ + public static string? GetPageSource(this IAutomatedApp app) => + app.Commands.Execute(CommonCommandNames.GetPageSource) as string; + + public static IInMemoryFile? GetScreenshot(this IAutomatedApp app) => + app.Commands.Execute(CommonCommandNames.GetScreenshot) as IInMemoryFile; + + public static void Click(this IAutomatedApp app, IAutomatedAppElement element) => + element.Click(); + + public static void Click(this IAutomatedApp app, int x, int y) => + app.Commands.Execute(CommonCommandNames.ClickCoordinates, ("x", x), ("y", y)); +} diff --git a/src/DeviceRunners.UIAutomation/Commands/CommonCommandNames.cs b/src/DeviceRunners.UIAutomation/Commands/CommonCommandNames.cs new file mode 100644 index 0000000..63d8188 --- /dev/null +++ b/src/DeviceRunners.UIAutomation/Commands/CommonCommandNames.cs @@ -0,0 +1,11 @@ +namespace DeviceRunners.UIAutomation; + +public static class CommonCommandNames +{ + public const string GetPageSource = "getPageSource"; + public const string GetScreenshot = "getScreenshot"; + public const string DismissKeyboard = "dismissKeyboard"; + public const string GetElementText = "getElementText"; + public const string ClickElement = "clickElement"; + public const string ClickCoordinates = "clickCoordinates"; +} diff --git a/src/DeviceRunners.UIAutomation/IAutomatedApp.cs b/src/DeviceRunners.UIAutomation/IAutomatedApp.cs index be00ee5..cc48cd3 100644 --- a/src/DeviceRunners.UIAutomation/IAutomatedApp.cs +++ b/src/DeviceRunners.UIAutomation/IAutomatedApp.cs @@ -1,6 +1,6 @@ namespace DeviceRunners.UIAutomation; -public interface IAutomatedApp : IDisposable +public interface IAutomatedApp : IContainsElements { IAutomatedAppCommandManager Commands { get; } } diff --git a/src/DeviceRunners.UIAutomation/IAutomatedAppElement.cs b/src/DeviceRunners.UIAutomation/IAutomatedAppElement.cs new file mode 100644 index 0000000..17ab96a --- /dev/null +++ b/src/DeviceRunners.UIAutomation/IAutomatedAppElement.cs @@ -0,0 +1,6 @@ +namespace DeviceRunners.UIAutomation; + +public interface IAutomatedAppElement // : IContainsElements +{ + IAutomatedApp App { get; } +} diff --git a/src/DeviceRunners.UIAutomation/IBy.cs b/src/DeviceRunners.UIAutomation/IBy.cs new file mode 100644 index 0000000..afcdaab --- /dev/null +++ b/src/DeviceRunners.UIAutomation/IBy.cs @@ -0,0 +1,6 @@ +namespace DeviceRunners.UIAutomation; + +public interface IBy +{ + void Selector(string selector, string value); +} diff --git a/src/DeviceRunners.UIAutomation/IContainsElements.cs b/src/DeviceRunners.UIAutomation/IContainsElements.cs new file mode 100644 index 0000000..7ee9486 --- /dev/null +++ b/src/DeviceRunners.UIAutomation/IContainsElements.cs @@ -0,0 +1,8 @@ +namespace DeviceRunners.UIAutomation; + +public interface IContainsElements +{ + IAutomatedAppElement FindElement(Action by); + + IReadOnlyList FindElements(Action by); +} diff --git a/test/DeviceRunners.UIAutomation.Appium.Tests/AutomationTestSuiteBuilderTests.cs b/test/DeviceRunners.UIAutomation.Appium.Tests/AutomationTestSuiteBuilderTests.cs new file mode 100644 index 0000000..90e9d40 --- /dev/null +++ b/test/DeviceRunners.UIAutomation.Appium.Tests/AutomationTestSuiteBuilderTests.cs @@ -0,0 +1,51 @@ +using DeviceRunners.UIAutomation; +using DeviceRunners.UIAutomation.Appium; + +using NSubstitute; + +using Xunit; + +namespace UIAutomationAppiumTests; + +public class AutomationTestSuiteBuilderTests : IDisposable +{ + AutomationTestSuite? suite; + + [Fact] + public void CanBuild() + { + suite = AutomationTestSuiteBuilder.Create() + .AddAppium(options => { }) + .Build(); + + Assert.NotNull(suite); + } + + [Fact] + public void CanAddFramework() + { + suite = AutomationTestSuiteBuilder.Create() + .AddAppium(options => { }) + .Build(); + + var framework = Assert.Single(suite.Frameworks); + Assert.IsType(framework); + } + + [Fact] + public void AppiumDefaultsAreCorrect() + { + suite = AutomationTestSuiteBuilder.Create() + .AddAppium(options => { }) + .Build(); + + var framework = Assert.Single(suite.Frameworks); + var appium = Assert.IsType(framework); + Assert.Equal("http://127.0.0.1:14723/", appium.ServiceManager.Service.ServiceUrl.AbsoluteUri); + } + + public void Dispose() + { + suite?.Dispose(); + } +} diff --git a/test/DeviceRunners.UIAutomation.Appium.Tests/DeviceRunners.UIAutomation.Appium.Tests.csproj b/test/DeviceRunners.UIAutomation.Appium.Tests/DeviceRunners.UIAutomation.Appium.Tests.csproj new file mode 100644 index 0000000..9a00e4a --- /dev/null +++ b/test/DeviceRunners.UIAutomation.Appium.Tests/DeviceRunners.UIAutomation.Appium.Tests.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + true + UIAutomationAppiumTests + + + + + + + + + + + + + + + + diff --git a/test/DeviceRunners.UIAutomation.Tests/AutomationTestSuiteBuilderTests.cs b/test/DeviceRunners.UIAutomation.Tests/AutomationTestSuiteBuilderTests.cs new file mode 100644 index 0000000..71cdeb7 --- /dev/null +++ b/test/DeviceRunners.UIAutomation.Tests/AutomationTestSuiteBuilderTests.cs @@ -0,0 +1,60 @@ +using DeviceRunners.UIAutomation; + +using NSubstitute; + +using Xunit; + +namespace UIAutomationTests; + +public class AutomationTestSuiteBuilderTests +{ + [Fact] + public void DefaultsDoNotThrow() + { + var builder = AutomationTestSuiteBuilder.Create(); + + Assert.NotNull(builder); + } + + [Fact] + public void CanBuild() + { + var builder = AutomationTestSuiteBuilder.Create(); + + var suite = builder.Build(); + + Assert.NotNull(suite); + } + + [Fact] + public void CanAddFramework() + { + var builder = AutomationTestSuiteBuilder.Create(); + + var framework = Substitute.For(); + builder.AddAutomationFramework(framework); + + var suite = builder.Build(); + + Assert.Equal([framework], suite.Frameworks); + } + + [Fact] + public void CanAddFrameworkAndApp() + { + var builder = AutomationTestSuiteBuilder.Create(); + + var options = Substitute.For(); + options.Key.Returns("test_app"); + + var framework = Substitute.For(); + framework.AvailableApps.Returns([options]); + + builder.AddAutomationFramework(framework); + + var suite = builder.Build(); + + Assert.Equal([framework], suite.Frameworks); + Assert.Equal(["test_app"], suite.AvailableApps); + } +} diff --git a/test/DeviceRunners.UIAutomation.Tests/AutomationTestSuiteTests.cs b/test/DeviceRunners.UIAutomation.Tests/AutomationTestSuiteTests.cs new file mode 100644 index 0000000..06ef711 --- /dev/null +++ b/test/DeviceRunners.UIAutomation.Tests/AutomationTestSuiteTests.cs @@ -0,0 +1,92 @@ +using DeviceRunners.UIAutomation; + +using NSubstitute; + +using Xunit; + +namespace UIAutomationTests; + +public class AutomationTestSuiteTests +{ + [Fact] + public void DefaultsDoNotThrow() + { + var builder = AutomationTestSuiteBuilder.Create(); + var suite = builder.Build(); + + Assert.NotNull(suite); + Assert.Empty(suite.AvailableApps); + Assert.Empty(suite.InstantiatedApps); + } + + [Fact] + public void RequestingInvalidAppThrows() + { + var framework = Substitute.For(); + var suite = BuildTestSuite(framework); + + Assert.Equal([framework], suite.Frameworks); + Assert.Throws(() => suite.GetApp("bad_test_app")); + } + + [Fact] + public void GetAppReturnsUnstartedAppWhenNotInstantiated() + { + var app = Substitute.For(); + var suite = BuildTestSuite(app: app); + + Assert.Equal(app, suite.GetApp("test_app")); + } + + [Fact] + public void StartAppReturnsAppAfterStarting() + { + var app = Substitute.For(); + var framework = Substitute.For(); + var suite = BuildTestSuite(framework, app); + + Assert.Equal(app, suite.StartApp("test_app")); + Assert.Equal(app, suite.GetApp("test_app")); + + framework.Received().CreateApp(Arg.Any()); + framework.Received().StartApp(Arg.Any()); + framework.DidNotReceive().RestartApp(Arg.Any()); + framework.DidNotReceive().StopApp(Arg.Any()); + } + + [Fact] + public void RestartAppReturnsAppAfterStartingButNotActuallyRestarting() + { + var app = Substitute.For(); + var framework = Substitute.For(); + var suite = BuildTestSuite(framework, app); + + Assert.Equal(app, suite.RestartApp("test_app")); + Assert.Equal(app, suite.GetApp("test_app")); + + framework.Received().CreateApp(Arg.Any()); + framework.Received().StartApp(Arg.Any()); + framework.DidNotReceive().RestartApp(Arg.Any()); + framework.DidNotReceive().StopApp(Arg.Any()); + } + + private static AutomationTestSuite BuildTestSuite(IAutomationFramework? framework = null, IAutomatedApp? app = null) + { + var builder = AutomationTestSuiteBuilder.Create(); + + var options = Substitute.For(); + options.Key.Returns("test_app"); + + app ??= Substitute.For(); + + framework ??= Substitute.For(); + framework.AvailableApps.Returns([options]); + framework.CreateApp(Arg.Is(options)).Returns(app); + + builder.AddAutomationFramework(framework); + + var suite = builder.Build(); + + return suite; + } +} diff --git a/test/DeviceRunners.UIAutomation.Tests/Commands/AutomatedAppCommandManagerTests.cs b/test/DeviceRunners.UIAutomation.Tests/Commands/AutomatedAppCommandManagerTests.cs new file mode 100644 index 0000000..172542c --- /dev/null +++ b/test/DeviceRunners.UIAutomation.Tests/Commands/AutomatedAppCommandManagerTests.cs @@ -0,0 +1,86 @@ +using DeviceRunners.UIAutomation; + +using NSubstitute; + +using Xunit; + +namespace UIAutomationTests.Commands; + +public class AutomatedAppCommandManagerTests +{ + [Fact] + public void AppPropertyIsCorrect() + { + var app = Substitute.For(); + + var manager = new AutomatedAppCommandManager(app, []); + + Assert.Equal(app, manager.App); + } + + [Fact] + public void CommandsAreAvailable() + { + var app = Substitute.For(); + + var command = Substitute.For(); + command.Name.Returns("Test"); + + var manager = new AutomatedAppCommandManager(app, [command]); + + Assert.True(manager.ContainsCommand("Test"), "The 'Test' command was not found."); + Assert.False(manager.ContainsCommand("Foo"), "The 'Foo' command was somehow found."); + } + + [Fact] + public void AvailableCommandsCanBeExecuted() + { + var app = Substitute.For(); + + var command = Substitute.For(); + command.Name.Returns("Test"); + command.Execute(Arg.Any(), Arg.Any>()).Returns("Success"); + + var manager = new AutomatedAppCommandManager(app, [command]); + + var result = manager.Execute("Test"); + + command.Received().Execute(app, null); + + Assert.Equal("Success", result); + } + + [Fact] + public void CommandParametersArePassedToTheCommand() + { + var app = Substitute.For(); + + var param = new Dictionary(); + + var command = Substitute.For(); + command.Name.Returns("Test"); + command.Execute(Arg.Any(), Arg.Any>()).Returns("Success"); + + var manager = new AutomatedAppCommandManager(app, [command]); + + var result = manager.Execute("Test", param); + + command.Received().Execute(app, param); + + Assert.Equal("Success", result); + } + + [Fact] + public void UnavailableCommandsCannotBeExecuted() + { + var app = Substitute.For(); + + var command = Substitute.For(); + command.Name.Returns("Test"); + command.Execute(Arg.Any(), Arg.Any>()).Returns(null); + + var manager = new AutomatedAppCommandManager(app, [command]); + + Assert.Throws(() => manager.Execute("Foo")); + } +} diff --git a/test/DeviceRunners.UIAutomation.Tests/Commands/AutomatedAppCommandTests.cs b/test/DeviceRunners.UIAutomation.Tests/Commands/AutomatedAppCommandTests.cs new file mode 100644 index 0000000..e4370a5 --- /dev/null +++ b/test/DeviceRunners.UIAutomation.Tests/Commands/AutomatedAppCommandTests.cs @@ -0,0 +1,82 @@ +using DeviceRunners.UIAutomation; + +using NSubstitute; + +using Xunit; + +namespace UIAutomationTests.Commands; + +public class AutomatedAppCommandTests +{ + [Fact] + public void CommandWithExactAppTypeExecutes() + { + var command = Substitute.For>("Test"); + command.Execute(Arg.Any(), Arg.Any>()).Returns("Success"); + + var app = new TestApp([command]); + + Assert.Equal("Test", command.Name); + + var result = command.Execute(app); + + command.Received().Execute(app); + Assert.Equal("Success", result); + } + + [Fact] + public void CommandWithDerivedAppTypeExecutes() + { + var command = Substitute.For>("Test"); + command.Execute(Arg.Any(), Arg.Any>()).Returns("Success"); + + Assert.Equal("Test", command.Name); + + var app = new DerivedApp([command]); + var result = command.Execute(app); + + command.Received().Execute(app); + Assert.Equal("Success", result); + } + + [Fact] + public void CommandWithAnotherAppTypeExecutes() + { + var command = Substitute.For>("Test"); + command.Execute(Arg.Any(), Arg.Any>()).Returns("Success"); + + Assert.Equal("Test", command.Name); + + var app = new AnotherApp([command]); + + Assert.Throws("app", () => ((IAutomatedAppCommand)command).Execute(app)); + } + + public class TestApp : IAutomatedApp + { + public TestApp(IReadOnlyList commands) + { + Commands = new AutomatedAppCommandManager(this, commands); + } + + public IAutomatedAppCommandManager Commands { get; } + } + + public class DerivedApp : TestApp + { + public DerivedApp(IReadOnlyList commands) + : base(commands) + { + } + } + + public class AnotherApp : IAutomatedApp + { + public AnotherApp(IReadOnlyList commands) + { + Commands = new AutomatedAppCommandManager(this, commands); + } + + public IAutomatedAppCommandManager Commands { get; } + } +} diff --git a/test/DeviceRunners.UIAutomation.Tests/Commands/AutomatedAppOptionsBuilderExtensions.cs b/test/DeviceRunners.UIAutomation.Tests/Commands/AutomatedAppOptionsBuilderExtensions.cs new file mode 100644 index 0000000..a29c7bc --- /dev/null +++ b/test/DeviceRunners.UIAutomation.Tests/Commands/AutomatedAppOptionsBuilderExtensions.cs @@ -0,0 +1,30 @@ +using DeviceRunners.UIAutomation; + +using NSubstitute; + +using Xunit; + +namespace UIAutomationTests.Commands; + +public class AutomatedAppOptionsBuilderExtensions +{ + [Fact] + public void AddCommandAddsTheCommand() + { + var command = Substitute.For(); + + var builder = new TestOptionsBuilder(); + builder.AddCommand(command); + + Assert.Contains(command, builder.Commands); + } + + class TestOptionsBuilder : IAutomatedAppOptionsBuilder + { + public List Commands { get; } = []; + + void IAutomatedAppOptionsBuilder.AddCommand(IAutomatedAppCommand command) => Commands.Add(command); + + public IAutomatedAppOptions Build() => throw new NotImplementedException(); + } +} diff --git a/test/DeviceRunners.UIAutomation.Tests/DeviceRunners.UIAutomation.Tests.csproj b/test/DeviceRunners.UIAutomation.Tests/DeviceRunners.UIAutomation.Tests.csproj new file mode 100644 index 0000000..6a11328 --- /dev/null +++ b/test/DeviceRunners.UIAutomation.Tests/DeviceRunners.UIAutomation.Tests.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + true + UIAutomationTests + + + + + + + + + + + + + + +