diff --git a/Samples/C#/StickyNotesTest/Properties/AssemblyInfo.cs b/Samples/C#/StickyNotesTest/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..222af3b0 --- /dev/null +++ b/Samples/C#/StickyNotesTest/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +//****************************************************************************** +// +// Copyright (c) 2018 Microsoft Corporation. All rights reserved. +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//****************************************************************************** + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("StickyNotesTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("StickyNotesTest")] +[assembly: AssemblyCopyright("Copyright © 2018 Microsoft Corporation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("9ed00387-c536-4ad7-9228-9fb87d3a2fd7")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/C#/StickyNotesTest/README.md b/Samples/C#/StickyNotesTest/README.md new file mode 100644 index 00000000..78b608ad --- /dev/null +++ b/Samples/C#/StickyNotesTest/README.md @@ -0,0 +1,101 @@ +# StickyNotesTest + +StickyNotesTest is a sample test project that demonstrates how to perform pen interactions through the actions API using the Windows 10 built-in **Sticky Notes** application. + +This test project highlights some common pen interactions below. +- Drawing using simple pen strokes +- Drawing strokes with additional parameters such as pressure, twist, and tilt angles +- Erasing previously drawn stroke by using eraser button + + +## Requirements + +- Windows 10 PC with the latest Windows 10 version (Version 1607 or later) +- Microsoft Visual Studio 2017 or later + + +## Getting Started + +1. [Run](../../../README.md#installing-and-running-windows-application-driver) `WinAppDriver.exe` on the test device +1. Open `StickyNotesTest.sln` in Visual Studio +2. Select **Test** > **Windows** > **Test Explorer** +3. Select **Run All** on the test pane or through menu **Test** > **Run** > **All Tests** + +> Once the project is successfully built, you can use the **TestExplorer** to pick and choose the test scenario(s) to run + + +## Tutorial + +### Including proper appium-dotnet-driver NuGet package + +Pen input support in Windows Application Driver is accessed using W3C WebDriver Actions API. Currently this feature is only supported +through a temporary `WinAppDriver.Preview.Appium.WebDriver` instead of the official `Appium.WebDriver` NuGet package. This sample test +is using this temporary NuGet package as shown in [packages.config](./packages.config). + +To make use all the pen features through the library in the NuGet package above, use the `OpenQA.Selenium.Appium.Interactions.PointerInputDevice` +instead of the `OpenQA.Selenium.Interactions.PointerInputDevice`. + +### Drawing a pen stroke + +A pen stroke is a collection of **PointerDown**, **PointerMove**, and **PointerUp** in an **ActionSequence** as shown below. +```c# +PointerInputDevice penDevice = new PointerInputDevice(PointerKind.Pen); +ActionSequence sequence = new ActionSequence(penDevice, 0); + +// Draw line AB from point A (0, 0) to B (10, 10) +sequence.AddAction(penDevice.CreatePointerMove(CoordinateOrigin.Viewport, 0, 0, TimeSpan.Zero)); +sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenContact)); +sequence.AddAction(penDevice.CreatePointerMove(CoordinateOrigin.Viewport, 10, 10, TimeSpan.Zero)); +sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenContact)); + +session.PerformActions(new List { sequence }); +``` + +### Drawing with additional pen attributes + +`OpenQA.Selenium.Appium.Interactions.PenInfo` class contains all the supported attributes for pen input such as **Pressure**, **Twist**, +**TiltX**, and **TiltY**. An instance containing any of the attribute can be passed into any pointer event creation. For example: +```c# +PointerInputDevice penDevice = new PointerInputDevice(PointerKind.Pen); +ActionSequence sequence = new ActionSequence(penDevice, 0); +PenInfo penExtraAttributes = new PenInfo { TiltX = 45, TiltY = 45, Twist = 45 }; + +// Draw line AB from point A (0, 0) to B (10, 10) with attributes defined in penExtraAttributes +sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenContact, penExtraAttributes)); +sequence.AddAction(penDevice.CreatePointerMove(CoordinateOrigin.Pointer, 10, 10, TimeSpan.Zero, new PenInfo { Pressure = 1f })); +sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenContact)); + +session.PerformActions(new List { sequence }); +``` + +### Erasing a pen stroke + +When inking on a canvas, a pen stroke can be erased by drawing another stroke on top of it while depressing the **Eraser** button. +Notice the use of `PointerButton.Eraser` instead of `PointerButton.PenContact`. For example: +```c# +PointerInputDevice penDevice = new PointerInputDevice(PointerKind.Pen); +ActionSequence sequence = new ActionSequence(penDevice, 0); + +// Erase line AB by pressing PenEraser (Pen tail end/eraser button) on the line AB +sequence.AddAction(penDevice.CreatePointerMove(CoordinateOrigin.Viewport, 0, 0, TimeSpan.Zero)); +sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenEraser)); +sequence.AddAction(penDevice.CreatePointerMove(CoordinateOrigin.Viewport, 10, 10, TimeSpan.Zero)); +sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenEraser)); + +session.PerformActions(new List { sequence }); +``` + +### Specifying pointer move coordinates + +Windows Application Driver supports all W3C WebDriver pointer action **origins** as follows: +1. **Viewport** - **X** and **Y** are relative to the application session window +2. **Pointer** - **X** and **Y** are relative to the current **X** and **Y** position +3. **Element** - **X** and **Y** are relative to the center point of the element + + +## Adding/Updating Test Scenario + +Please follow the guidelines below to maintain test reliability and conciseness: +1. Group test methods based on the UI view page such as Alarm, Stopwatch, Timer, etc. +2. Maintain original state after test runs and keep clean state in between tests when possible +3. Only add tests that provide additional value to the sample diff --git a/Samples/C#/StickyNotesTest/ScenarioPen.cs b/Samples/C#/StickyNotesTest/ScenarioPen.cs new file mode 100644 index 00000000..54e8679e --- /dev/null +++ b/Samples/C#/StickyNotesTest/ScenarioPen.cs @@ -0,0 +1,287 @@ +//****************************************************************************** +// +// Copyright (c) 2018 Microsoft Corporation. All rights reserved. +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//****************************************************************************** + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Drawing; +using System.Threading; +using System.Collections.Generic; +using OpenQA.Selenium; +using OpenQA.Selenium.Remote; +using OpenQA.Selenium.Interactions; +using OpenQA.Selenium.Appium.Interactions; +using OpenQA.Selenium.Appium.Windows; + +// Define an alias to OpenQA.Selenium.Appium.Interactions.PointerInputDevice to hide +// inherited OpenQA.Selenium.Interactions.PointerInputDevice that causes ambiguity. +// In the future, all functions of OpenQA.Selenium.Appium.Interactions should be moved +// up to OpenQA.Selenium.Interactions and this alias can simply be removed. +using PointerInputDevice = OpenQA.Selenium.Appium.Interactions.PointerInputDevice; + +namespace StickyNotesTest +{ + [TestClass] + public class ScenarioPen : StickyNotesSession + { + private WindowsDriver newStickyNoteSession; + private WindowsElement inkCanvas; + + [TestMethod] + public void DrawBasicSquare() + { + Point canvasCoordinate = inkCanvas.Coordinates.LocationInViewport; + Size squareSize = new Size(inkCanvas.Size.Width * 3 / 5, inkCanvas.Size.Height * 3 / 5); + Point A = new Point(canvasCoordinate.X + inkCanvas.Size.Width / 5, canvasCoordinate.Y + inkCanvas.Size.Height / 5); + + // A B + // ┌──────┐ Draw a basic ABCD square using Pen through the Actions API + // │ │ in viewport(default) origin mode: + // │ │ - X is absolute horizontal position in the session window + // └──────┘ - Y is absolute vertical position in the session window + // D C + PointerInputDevice penDevice = new PointerInputDevice(PointerKind.Pen); + ActionSequence sequence = new ActionSequence(penDevice, 0); + + // Draw line AB from point A to B + sequence.AddAction(penDevice.CreatePointerMove(CoordinateOrigin.Viewport, A.X, A.Y, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenContact)); + sequence.AddAction(penDevice.CreatePointerMove(CoordinateOrigin.Viewport, A.X + squareSize.Width, A.Y, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenContact)); + + // Draw line BC from point B to C + sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenContact)); + sequence.AddAction(penDevice.CreatePointerMove(CoordinateOrigin.Viewport, A.X + squareSize.Width, A.Y + squareSize.Height, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenContact)); + + // Draw line CD from point C to D + sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenContact)); + sequence.AddAction(penDevice.CreatePointerMove(CoordinateOrigin.Viewport, A.X, A.Y + squareSize.Height, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenContact)); + + // Draw line DA from point D to A + sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenContact)); + sequence.AddAction(penDevice.CreatePointerMove(CoordinateOrigin.Viewport, A.X, A.Y, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenContact)); + + newStickyNoteSession.PerformActions(new List { sequence }); + + try + { + var result = newStickyNoteSession.FindElementByAccessibilityId("RichEditBox"); + Assert.Fail("RichEditBox should not be defined anymore after a pen input is successfully performed."); + } + catch { } + } + + [TestMethod] + public void DrawBasicSquareWithExtraAttributes() + { + Point canvasCoordinate = inkCanvas.Coordinates.LocationInViewport; + Size squareSize = new Size(inkCanvas.Size.Width * 3 / 5, inkCanvas.Size.Height * 3 / 5); + Point A = new Point(canvasCoordinate.X + inkCanvas.Size.Width / 5, canvasCoordinate.Y + inkCanvas.Size.Height / 5); + + // A B + // ┌──────┐ Draw a basic ABCD square using Pen through the Actions API + // │ │ in pointer origin mode: + // │ │ - X is relative to the previous X position in this session + // └──────┘ - Y is relative to the previous Y position in this session + // D C + PointerInputDevice penDevice = new PointerInputDevice(PointerKind.Pen); + ActionSequence sequence = new ActionSequence(penDevice, 0); + PenInfo penExtraAttributes = new PenInfo { TiltX = 45, TiltY = 45, Twist = 45 }; + + // Draw line AB from point A to B with attributes defined in penExtraAttributes + sequence.AddAction(penDevice.CreatePointerMove(CoordinateOrigin.Pointer, A.X, A.Y, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenContact, penExtraAttributes)); + sequence.AddAction(penDevice.CreatePointerMove(CoordinateOrigin.Pointer, squareSize.Width, 0, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenContact)); + + // Draw line BC from point B to C and apply maximum (1.0f) pressure as the pen draw between the points + sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenContact)); + sequence.AddAction(penDevice.CreatePointerMove(CoordinateOrigin.Pointer, 0, squareSize.Height, TimeSpan.Zero, new PenInfo { Pressure = 1f })); + sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenContact)); + + // Draw line CD from point C to D and keep the maximum pressure by not changing the pressure attribute + sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenContact)); + sequence.AddAction(penDevice.CreatePointerMove(CoordinateOrigin.Pointer, -squareSize.Width, 0, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenContact)); + + // Draw line DA from point D to A and reduce the pressure to minimum (0.0f) as the pen draw between the points + sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenContact)); + sequence.AddAction(penDevice.CreatePointerMove(CoordinateOrigin.Pointer, 0, -squareSize.Height, TimeSpan.Zero, new PenInfo { Pressure = 0f })); + sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenContact)); + + newStickyNoteSession.PerformActions(new List { sequence }); + + try + { + var result = newStickyNoteSession.FindElementByAccessibilityId("RichEditBox"); + Assert.Fail("RichEditBox should not be defined anymore after a pen input is successfully performed."); + } + catch { } + } + + [TestMethod] + public void DrawSquareAndDeleteStrokes() + { + int offset = 400; + // A B + // ┌──────┐ Draw a basic ABCD square using Pen through the Actions API + // │ │ in pointer element mode: + // │ │ - X is relative to the horizontal element center point + // └──────┘ - Y is relative to the vertical element center point + // D C + PointerInputDevice penDevice = new PointerInputDevice(PointerKind.Pen); + ActionSequence sequence = new ActionSequence(penDevice, 0); + + // Draw line CD from point C to D and apply 30% pen pressure + sequence.AddAction(penDevice.CreatePointerMove(inkCanvas, offset, offset, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenContact, new PenInfo { Pressure = 0.3f })); + sequence.AddAction(penDevice.CreatePointerMove(inkCanvas, -offset, offset, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenContact)); + + // Draw line DA from point D to A + sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenContact)); + sequence.AddAction(penDevice.CreatePointerMove(inkCanvas, -offset, -offset, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenContact)); + + // Draw line AB from point A to B + sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenContact)); + sequence.AddAction(penDevice.CreatePointerMove(inkCanvas, offset, -offset, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenContact)); + + // Draw line BC from point B to C + sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenContact)); + sequence.AddAction(penDevice.CreatePointerMove(inkCanvas, offset, offset, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenContact)); + + // Draw diagonal line CA from point C straight to A + sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenContact)); + sequence.AddAction(penDevice.CreatePointerMove(inkCanvas, -offset, -offset, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenContact)); + + // Draw diagonal line DB from point D straight to B + sequence.AddAction(penDevice.CreatePointerMove(inkCanvas, -offset, offset, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenContact)); + sequence.AddAction(penDevice.CreatePointerMove(inkCanvas, offset, -offset, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenContact)); + + // Erase line AB by pressing PenEraser (Pen tail end/eraser button) on the line AB from right to left + sequence.AddAction(penDevice.CreatePointerMove(inkCanvas, offset / 2, -offset, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenEraser)); + sequence.AddAction(penDevice.CreatePointerMove(inkCanvas, -offset / 2, -offset, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenEraser)); + + // Erase line CD by pressing PenEraser (Pen tail end/eraser button) on the line CD from left to right + sequence.AddAction(penDevice.CreatePointerMove(inkCanvas, -offset / 2, offset, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerDown(PointerButton.PenEraser)); + sequence.AddAction(penDevice.CreatePointerMove(inkCanvas, offset / 2, offset, TimeSpan.Zero)); + sequence.AddAction(penDevice.CreatePointerUp(PointerButton.PenEraser)); + + newStickyNoteSession.PerformActions(new List { sequence }); + + try + { + var result = newStickyNoteSession.FindElementByAccessibilityId("RichEditBox"); + Assert.Fail("RichEditBox should not be defined anymore after a pen input is successfully performed."); + } + catch { } + } + + [ClassInitialize] + public static void ClassInitialize(TestContext context) + { + // Create session to launch or bring up Sticky Notes application + Setup(context); + } + + [ClassCleanup] + public static void ClassCleanup() + { + TearDown(); + } + + [TestInitialize] + public void CreateNewStickyNote() + { + const int stickyNoteWidth = 1000; + const int stickyNoteHeight = 1000; + const int stickyNotePositionX = 100; + const int stickyNotePositionY = 100; + + // Preserve existing or previously opened Sticky Notes by keeping track of them on initialize + var openedStickyNotesWindowsBefore = session.FindElementsByClassName("ApplicationFrameWindow"); + Assert.IsNotNull(openedStickyNotesWindowsBefore); + Assert.IsTrue(openedStickyNotesWindowsBefore.Count > 0); + + // Create a new Sticky Note by pressing Ctrl + N + openedStickyNotesWindowsBefore[0].SendKeys(Keys.Control + "n" + Keys.Control); + Thread.Sleep(TimeSpan.FromSeconds(2)); + + var openedStickyNotesWindowsAfter = session.FindElementsByClassName("ApplicationFrameWindow"); + Assert.IsNotNull(openedStickyNotesWindowsAfter); + Assert.AreEqual(openedStickyNotesWindowsBefore.Count + 1, openedStickyNotesWindowsAfter.Count); + + // Identify the newly opened Sticky Note by removing the previously opened ones from the list + List openedStickyNotes = new List(openedStickyNotesWindowsAfter); + foreach (var preExistingStickyNote in openedStickyNotesWindowsBefore) + { + openedStickyNotes.Remove(preExistingStickyNote); + } + Assert.AreEqual(1, openedStickyNotes.Count); + + // Create a new session based from the newly opened Sticky Notes window + var newStickyNoteWindowHandle = openedStickyNotes[0].GetAttribute("NativeWindowHandle"); + newStickyNoteWindowHandle = (int.Parse(newStickyNoteWindowHandle)).ToString("x"); // Convert to Hex + DesiredCapabilities appCapabilities = new DesiredCapabilities(); + appCapabilities.SetCapability("appTopLevelWindow", newStickyNoteWindowHandle); + appCapabilities.SetCapability("deviceName", "WindowsPC"); + newStickyNoteSession = new WindowsDriver(new Uri(WindowsApplicationDriverUrl), appCapabilities); + Assert.IsNotNull(newStickyNoteSession); + + // Resize and re-position the Sticky Notes window we are working with + newStickyNoteSession.Manage().Window.Size = new Size(stickyNoteWidth, stickyNoteHeight); + newStickyNoteSession.Manage().Window.Position = new Point(stickyNotePositionX, stickyNotePositionY); + + // Verify that this Sticky Notes is indeed new. Newly created Sticky Notes has both + // RichEditBox and InkCanvas in the UI tree. Once modified by a pen or key input, + // it will only contain one or the other. + Assert.IsNotNull(newStickyNoteSession.FindElementByAccessibilityId("RichEditBox")); + inkCanvas = newStickyNoteSession.FindElementByAccessibilityId("InkCanvas"); + Assert.IsNotNull(inkCanvas); + } + + [TestCleanup] + public void DeleteStickyNote() + { + if (newStickyNoteSession != null) + { + // Create a new Sticky Note by pressing Ctrl + N + newStickyNoteSession.Keyboard.SendKeys(Keys.Control + "d" + Keys.Control); + Thread.Sleep(TimeSpan.FromSeconds(2)); + + try + { + // If a delete confirmation dialog is displayed, press Delete to proceed + newStickyNoteSession.FindElementByAccessibilityId("YesButton").Click(); + } + catch { } + } + + inkCanvas = null; + } + } +} diff --git a/Samples/C#/StickyNotesTest/StickyNotesSession.cs b/Samples/C#/StickyNotesTest/StickyNotesSession.cs new file mode 100644 index 00000000..c1d549a5 --- /dev/null +++ b/Samples/C#/StickyNotesTest/StickyNotesSession.cs @@ -0,0 +1,101 @@ +//****************************************************************************** +// +// Copyright (c) 2018 Microsoft Corporation. All rights reserved. +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//****************************************************************************** + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenQA.Selenium.Appium.Windows; +using OpenQA.Selenium.Remote; +using System; + +namespace StickyNotesTest +{ + public class StickyNotesSession + { + // Note: append /wd/hub to the URL if you're directing the test at Appium + protected const string WindowsApplicationDriverUrl = "http://127.0.0.1:4723"; + private const string StickyNotesAppId = @"Microsoft.MicrosoftStickyNotes_8wekyb3d8bbwe!App"; + + protected static WindowsDriver session; + + public static void Setup(TestContext context) + { + // Launch StickyNotes application if it is not yet launched + if (session == null) + { + try + { + // Create a new session to laucnh or bring up Sticky Notes application + // Note: All sticky note windows are parented to Modern_Sticky_Top_Window pane + DesiredCapabilities appCapabilities = new DesiredCapabilities(); + appCapabilities.SetCapability("app", StickyNotesAppId); + appCapabilities.SetCapability("deviceName", "WindowsPC"); + session = new WindowsDriver(new Uri(WindowsApplicationDriverUrl), appCapabilities); + } + catch + { + // When Sticky Notes application was previously launched, the creation above may fail. + // In such failure, simply look for the Modern_Sticky_Top_Window pane using the Desktop + // session and create a new session based on the located top window pane. + DesiredCapabilities desktopCapabilities = new DesiredCapabilities(); + desktopCapabilities.SetCapability("app", "Root"); + desktopCapabilities.SetCapability("deviceName", "WindowsPC"); + var desktopSession = new WindowsDriver(new Uri(WindowsApplicationDriverUrl), desktopCapabilities); + + var StickyNotesTopLevelWindow = desktopSession.FindElementByClassName("Modern_Sticky_Top_Window"); + var StickyNotesTopLevelWindowHandle = StickyNotesTopLevelWindow.GetAttribute("NativeWindowHandle"); + StickyNotesTopLevelWindowHandle = (int.Parse(StickyNotesTopLevelWindowHandle)).ToString("x"); // Convert to Hex + + DesiredCapabilities appCapabilities = new DesiredCapabilities(); + appCapabilities.SetCapability("appTopLevelWindow", StickyNotesTopLevelWindowHandle); + appCapabilities.SetCapability("deviceName", "WindowsPC"); + session = new WindowsDriver(new Uri(WindowsApplicationDriverUrl), appCapabilities); + } + Assert.IsNotNull(session); + + // Set implicit timeout to 1.5 seconds to make element search to retry every 500 ms for at most three times + session.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(1.5); + } + } + + public static void TearDown() + { + // Close the application and delete the session + if (session != null) + { + try + { + // Sticky Notes applciation can be closed by explicitly closing any of the opened Sticky Notes window. + // Create a new session based on any of opened Sticky Notes window and close it to close the application. + var openedStickyNotes = session.FindElementsByClassName("ApplicationFrameWindow"); + if (openedStickyNotes.Count > 0) + { + var newStickyNoteWindowHandle = openedStickyNotes[0].GetAttribute("NativeWindowHandle"); + newStickyNoteWindowHandle = (int.Parse(newStickyNoteWindowHandle)).ToString("x"); // Convert to Hex + + DesiredCapabilities appCapabilities = new DesiredCapabilities(); + appCapabilities.SetCapability("appTopLevelWindow", newStickyNoteWindowHandle); + appCapabilities.SetCapability("deviceName", "WindowsPC"); + var stickyNoteSession = new WindowsDriver(new Uri(WindowsApplicationDriverUrl), appCapabilities); + stickyNoteSession.Close(); + } + } + catch { } + + session.Quit(); + session = null; + } + } + } +} diff --git a/Samples/C#/StickyNotesTest/StickyNotesTest.csproj b/Samples/C#/StickyNotesTest/StickyNotesTest.csproj new file mode 100644 index 00000000..2eada89d --- /dev/null +++ b/Samples/C#/StickyNotesTest/StickyNotesTest.csproj @@ -0,0 +1,85 @@ + + + + + Debug + AnyCPU + {9ED00387-C536-4AD7-9228-9FB87D3A2FD7} + Library + Properties + StickyNotesTest + StickyNotesTest + v4.6.1 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + packages\Microsoft.WinAppDriver.Appium.WebDriver.1.0.1-Preview\lib\net45\appium-dotnet-driver.dll + + + packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll + + + packages\MSTest.TestFramework.1.2.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + packages\MSTest.TestFramework.1.2.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + + + + + + + packages\Selenium.WebDriver.3.8.0\lib\net45\WebDriver.dll + + + packages\Selenium.Support.3.8.0\lib\net45\WebDriver.Support.dll + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/Samples/C#/StickyNotesTest/StickyNotesTest.sln b/Samples/C#/StickyNotesTest/StickyNotesTest.sln new file mode 100644 index 00000000..e81a7f1d --- /dev/null +++ b/Samples/C#/StickyNotesTest/StickyNotesTest.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2027 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StickyNotesTest", "StickyNotesTest.csproj", "{9ED00387-C536-4AD7-9228-9FB87D3A2FD7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9ED00387-C536-4AD7-9228-9FB87D3A2FD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9ED00387-C536-4AD7-9228-9FB87D3A2FD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9ED00387-C536-4AD7-9228-9FB87D3A2FD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9ED00387-C536-4AD7-9228-9FB87D3A2FD7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CB5C6371-2720-46EF-9125-DA145F5DD82F} + EndGlobalSection +EndGlobal diff --git a/Samples/C#/StickyNotesTest/packages.config b/Samples/C#/StickyNotesTest/packages.config new file mode 100644 index 00000000..7606f96f --- /dev/null +++ b/Samples/C#/StickyNotesTest/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file