Added Calculator Standard Mode UI Tests (#501)

- Added the CalculatorUIFramework to handle the WinAppDriver logic.
- Added Standard Mode smoke tests and BVTs to the CalculatorUITests project.
- Removed old UI tests that did not use the CalculatorUIFramework
This commit is contained in:
Stephanie Anderl
2019-06-21 14:54:36 -07:00
committed by Matt Cooley
parent e9551e3774
commit 2517854836
19 changed files with 1427 additions and 169 deletions

View File

@@ -17,7 +17,9 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CalcViewModel", "CalcViewMo
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CalculatorUnitTests", "CalculatorUnitTests\CalculatorUnitTests.vcxproj", "{D3BAED2C-4B07-4E1D-8807-9D6499450349}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CalculatorUITests", "CalculatorUITests\CalculatorUITests.csproj", "{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CalculatorUITests", "CalculatorUITests\CalculatorUITests.csproj", "{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CalculatorUITestFramework", "CalculatorUITestFramework\CalculatorUITestFramework.csproj", "{96454213-94AF-457D-9DF9-B14F80E7770F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -119,6 +121,22 @@ Global
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Release|x64.Build.0 = Release|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Release|x86.ActiveCfg = Release|Any CPU
{B2C5ADFF-D6B5-48C1-BB8C-571BFD583D7F}.Release|x86.Build.0 = Release|Any CPU
{96454213-94AF-457D-9DF9-B14F80E7770F}.Debug|ARM.ActiveCfg = Debug|Any CPU
{96454213-94AF-457D-9DF9-B14F80E7770F}.Debug|ARM.Build.0 = Debug|Any CPU
{96454213-94AF-457D-9DF9-B14F80E7770F}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{96454213-94AF-457D-9DF9-B14F80E7770F}.Debug|ARM64.Build.0 = Debug|Any CPU
{96454213-94AF-457D-9DF9-B14F80E7770F}.Debug|x64.ActiveCfg = Debug|Any CPU
{96454213-94AF-457D-9DF9-B14F80E7770F}.Debug|x64.Build.0 = Debug|Any CPU
{96454213-94AF-457D-9DF9-B14F80E7770F}.Debug|x86.ActiveCfg = Debug|Any CPU
{96454213-94AF-457D-9DF9-B14F80E7770F}.Debug|x86.Build.0 = Debug|Any CPU
{96454213-94AF-457D-9DF9-B14F80E7770F}.Release|ARM.ActiveCfg = Release|Any CPU
{96454213-94AF-457D-9DF9-B14F80E7770F}.Release|ARM.Build.0 = Release|Any CPU
{96454213-94AF-457D-9DF9-B14F80E7770F}.Release|ARM64.ActiveCfg = Release|Any CPU
{96454213-94AF-457D-9DF9-B14F80E7770F}.Release|ARM64.Build.0 = Release|Any CPU
{96454213-94AF-457D-9DF9-B14F80E7770F}.Release|x64.ActiveCfg = Release|Any CPU
{96454213-94AF-457D-9DF9-B14F80E7770F}.Release|x64.Build.0 = Release|Any CPU
{96454213-94AF-457D-9DF9-B14F80E7770F}.Release|x86.ActiveCfg = Release|Any CPU
{96454213-94AF-457D-9DF9-B14F80E7770F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Appium.WebDriver" Version="4.0.0.6-beta" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;
using System.Collections.ObjectModel;
namespace CalculatorUITestFramework
{
public class HistoryPanel
{
private WindowsDriver<WindowsElement> session => WinAppDriver.Instance.CalculatorSession;
public WindowsElement HistoryLabel => this.session.TryFindElementByAccessibilityId("HistoryLabel");
public WindowsElement HistoryListView => this.session.TryFindElementByAccessibilityId("HistoryListView");
public WindowsElement ClearHistoryButton => this.session.TryFindElementByAccessibilityId("ClearHistory");
public WindowsElement HistoryEmptyLabel => this.session.TryFindElementByAccessibilityId("HistoryEmpty");
/// <summary>
/// Gets all of the history items listed in the History Pane.
/// </summary>
/// <returns>A readonly collection of history items.</returns>
public ReadOnlyCollection<AppiumWebElement> GetAllHistoryListViewItems()
{
this.HistoryLabel.Click();
this.HistoryListView.WaitForDisplayed();
return this.HistoryListView.FindElementsByClassName("ListViewItem");
}
/// <summary>
/// Opens the History Pane and clicks the delete button if it is visible.
/// </summary>
public void ClearHistory()
{
this.HistoryLabel.Click();
try
{
this.ClearHistoryButton.Click();
}
catch(WebDriverException ex)
{
if (ex.Message.Contains("element could not be located"))
{
Assert.IsNotNull(this.HistoryEmptyLabel);
return;
}
throw;
}
}
}
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;
using System.Collections.ObjectModel;
namespace CalculatorUITestFramework
{
public class MemoryPanel
{
private WindowsDriver<WindowsElement> session => WinAppDriver.Instance.CalculatorSession;
public WindowsElement MemoryClear => this.session.TryFindElementByAccessibilityId("ClearMemoryButton");
public WindowsElement MemRecall => this.session.TryFindElementByAccessibilityId("MemRecall");
public WindowsElement MemPlus => this.session.TryFindElementByAccessibilityId("MemPlus");
public WindowsElement MemMinus => this.session.TryFindElementByAccessibilityId("MemMinus");
public WindowsElement MemButton => this.session.TryFindElementByAccessibilityId("memButton");
public WindowsElement MemoryPane => this.session.TryFindElementByAccessibilityId("MemoryPanel");
public WindowsElement MemoryLabel => this.session.TryFindElementByAccessibilityId("MemoryLabel");
public WindowsElement MemoryListView => this.session.TryFindElementByAccessibilityId("MemoryListView");
public WindowsElement MemoryPaneEmptyLabel => this.session.TryFindElementByAccessibilityId("MemoryPaneEmpty");
/// <summary>
/// Opens the Memory Pane by clicking the Memory pivot label.
/// </summary>
public void OpenMemoryPanel()
{
this.MemoryLabel.Click();
this.MemoryPane.WaitForDisplayed();
}
/// <summary>
/// Gets all of the memory items listed in the Memory Pane.
/// </summary>
/// <returns>A readonly collection of memory items.</returns>
public ReadOnlyCollection<AppiumWebElement> GetAllMemoryListViewItems()
{
OpenMemoryPanel();
return this.MemoryListView.FindElementsByClassName("ListViewItem");
}
}
}

View File

@@ -0,0 +1,105 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using OpenQA.Selenium.Appium.Windows;
using System;
namespace CalculatorUITestFramework
{
public enum CalculatorMode
{
StandardCalculator,
ScientificCalculator,
ProgrammerCalculator,
DateCalculator,
Currency,
Volume,
Length,
Weight,
Temperature,
Energy,
Area,
Speed,
Time,
Power,
Data,
Pressure,
Angle
}
public class NavigationMenu
{
private WindowsDriver<WindowsElement> session => WinAppDriver.Instance.CalculatorSession;
public WindowsElement NavigationMenuButton => this.session.TryFindElementByAccessibilityId("TogglePaneButton");
public WindowsElement NavigationMenuPane => this.session.TryFindElementByClassName("SplitViewPane");
/// <summary>
/// Changes the mode using the navigation menu in the UI
/// </summary>
/// <param name="mode">The mode to be changed to</param>
public void ChangeCalculatorMode(CalculatorMode mode)
{
string modeAccessibilityId;
switch (mode)
{
case CalculatorMode.StandardCalculator:
modeAccessibilityId = "Standard";
break;
case CalculatorMode.ScientificCalculator:
modeAccessibilityId = "Scientific";
break;
case CalculatorMode.ProgrammerCalculator:
modeAccessibilityId = "Programmer";
break;
case CalculatorMode.DateCalculator:
modeAccessibilityId = "Date";
break;
case CalculatorMode.Currency:
modeAccessibilityId = "Currency";
break;
case CalculatorMode.Volume:
modeAccessibilityId = "Volume";
break;
case CalculatorMode.Length:
modeAccessibilityId = "Length";
break;
case CalculatorMode.Weight:
modeAccessibilityId = "Weight";
break;
case CalculatorMode.Temperature:
modeAccessibilityId = "Temperature";
break;
case CalculatorMode.Energy:
modeAccessibilityId = "Energy";
break;
case CalculatorMode.Area:
modeAccessibilityId = "Area";
break;
case CalculatorMode.Speed:
modeAccessibilityId = "Speed";
break;
case CalculatorMode.Time:
modeAccessibilityId = "Time";
break;
case CalculatorMode.Power:
modeAccessibilityId = "Power";
break;
case CalculatorMode.Data:
modeAccessibilityId = "Data";
break;
case CalculatorMode.Pressure:
modeAccessibilityId = "Pressure";
break;
case CalculatorMode.Angle:
modeAccessibilityId = "Angle";
break;
default:
throw (new ArgumentException("The mode is not valid"));
}
this.NavigationMenuButton.Click();
this.NavigationMenuPane.WaitForDisplayed();
this.session.TryFindElementByAccessibilityId(modeAccessibilityId).Click();
}
}
}

View File

@@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using OpenQA.Selenium.Appium.Windows;
using System;
namespace CalculatorUITestFramework
{
public class NumberPad
{
private WindowsDriver<WindowsElement> session => WinAppDriver.Instance.CalculatorSession;
public WindowsElement Num0Button => this.session.TryFindElementByAccessibilityId("num0Button");
public WindowsElement Num1Button => this.session.TryFindElementByAccessibilityId("num1Button");
public WindowsElement Num2Button => this.session.TryFindElementByAccessibilityId("num2Button");
public WindowsElement Num3Button => this.session.TryFindElementByAccessibilityId("num3Button");
public WindowsElement Num4Button => this.session.TryFindElementByAccessibilityId("num4Button");
public WindowsElement Num5Button => this.session.TryFindElementByAccessibilityId("num5Button");
public WindowsElement Num6Button => this.session.TryFindElementByAccessibilityId("num6Button");
public WindowsElement Num7Button => this.session.TryFindElementByAccessibilityId("num7Button");
public WindowsElement Num8Button => this.session.TryFindElementByAccessibilityId("num8Button");
public WindowsElement Num9Button => this.session.TryFindElementByAccessibilityId("num9Button");
public WindowsElement DecimalButton => this.session.TryFindElementByAccessibilityId("decimalSeparatorButton");
public WindowsElement NegateButton => this.session.TryFindElementByAccessibilityId("negateButton");
/// <summary>
/// Translates a number into the Calculator button clicks.
/// </summary>
/// <param name="number">Number to be entered into the calculator.</param>
public void Input(double number)
{
string numberStr = number.ToString();
if (numberStr.StartsWith("-"))
{
numberStr = numberStr.Substring(1) + "-";
}
foreach (char digit in numberStr)
{
switch (digit)
{
case '0':
this.Num0Button.Click();
break;
case '1':
this.Num1Button.Click();
break;
case '2':
this.Num2Button.Click();
break;
case '3':
this.Num3Button.Click();
break;
case '4':
this.Num4Button.Click();
break;
case '5':
this.Num5Button.Click();
break;
case '6':
this.Num6Button.Click();
break;
case '7':
this.Num7Button.Click();
break;
case '8':
this.Num8Button.Click();
break;
case '9':
this.Num9Button.Click();
break;
case '.':
this.DecimalButton.Click();
break;
case '-':
this.NegateButton.Click();
break;
default:
throw (new ArgumentException(String.Format("{0} is not valid", digit)));
}
}
}
}
}

View File

@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium.Appium.Windows;
namespace CalculatorUITestFramework
{
/// <summary>
/// This class contains the UI automation objects and helper methods available when the Calculator is in Standard Mode.
/// </summary>
public class StandardCalculatorPage
{
private WindowsDriver<WindowsElement> session => WinAppDriver.Instance.CalculatorSession;
public StandardOperatorsPanel StandardOperators = new StandardOperatorsPanel();
public MemoryPanel MemoryPanel = new MemoryPanel();
public HistoryPanel HistoryPanel = new HistoryPanel();
public NavigationMenu NavigationMenu = new NavigationMenu();
public WindowsElement Header => this.session.TryFindElementByAccessibilityId("Header");
public WindowsElement CalculatorResult => this.session.TryFindElementByAccessibilityId("CalculatorResults");
public void NavigateToStandardCalculator()
{
// Ensure that calculator is in standard mode
this.NavigationMenu.ChangeCalculatorMode(CalculatorMode.StandardCalculator);
Assert.IsNotNull(CalculatorResult);
}
/// <summary>
/// Clear the Calculator display, Memory Panel and optionally the History Panel
/// </summary>
public void ClearAll()
{
this.StandardOperators.ClearButton.Click();
this.MemoryPanel.MemoryClear.Click();
this.HistoryPanel.ClearHistory();
}
/// <summary>
/// Gets the text from the display control and removes the narrator text that is not displayed in the UI.
/// </summary>
/// <returns>The string shown in the UI.</returns>
public string GetCalculatorResultText()
{
return this.CalculatorResult.Text.Replace("Display is", string.Empty).Trim();
}
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using OpenQA.Selenium.Appium.Windows;
using System.Collections.Generic;
namespace CalculatorUITestFramework
{
/// <summary>
/// UI elements and helper methods to perform common mathematical standard operations.
/// </summary>
public class StandardOperatorsPanel
{
private WindowsDriver<WindowsElement> session => WinAppDriver.Instance.CalculatorSession;
public NumberPad NumberPad = new NumberPad();
public WindowsElement PercentButton => this.session.TryFindElementByAccessibilityId("percentButton");
public WindowsElement SquareRootButton => this.session.TryFindElementByAccessibilityId("squareRootButton");
public WindowsElement XPower2Button => this.session.TryFindElementByAccessibilityId("xpower2Button");
public WindowsElement XPower3Button => this.session.TryFindElementByAccessibilityId("xpower3Button");
public WindowsElement InvertButton => this.session.TryFindElementByAccessibilityId("invertButton");
public WindowsElement DivideButton => this.session.TryFindElementByAccessibilityId("divideButton");
public WindowsElement MultiplyButton => this.session.TryFindElementByAccessibilityId("multiplyButton");
public WindowsElement MinusButton => this.session.TryFindElementByAccessibilityId("minusButton");
public WindowsElement PlusButton => this.session.TryFindElementByAccessibilityId("plusButton");
public WindowsElement EqualButton => this.session.TryFindElementByAccessibilityId("equalButton");
public WindowsElement ClearEntryButton => this.session.TryFindElementByAccessibilityId("clearEntryButton");
public WindowsElement ClearButton => this.session.TryFindElementByAccessibilityId("clearButton");
public WindowsElement BackSpaceButton => this.session.TryFindElementByAccessibilityId("backSpaceButton");
}
}

View File

@@ -0,0 +1,84 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;
using System;
using System.Diagnostics;
namespace CalculatorUITestFramework
{
public sealed class WinAppDriver
{
private WindowsDriverLocalService windowsDriverService = null;
private const string calculatorAppId = "Microsoft.WindowsCalculator.Dev_8wekyb3d8bbwe!App";
private static WinAppDriver instance = null;
public static WinAppDriver Instance
{
get
{
if (instance == null)
{
instance = new WinAppDriver();
}
return instance;
}
}
public WindowsDriver<WindowsElement> CalculatorSession { get; private set; }
private WinAppDriver()
{
}
public void SetupCalculatorSession(TestContext context)
{
this.windowsDriverService = new WindowsDriverServiceBuilder().Build();
this.windowsDriverService.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
{
var outputData = e.Data?.Replace("\0", string.Empty);
if (!String.IsNullOrEmpty(outputData))
{
Console.WriteLine(outputData);
}
});
this.windowsDriverService.Start();
// Launch Calculator application if it is not yet launched
if (this.CalculatorSession == null)
{
// Create a new WinAppDriver session to bring up an instance of the Calculator application
// Note: Multiple calculator windows (instances) share the same process Id
var options = new AppiumOptions();
options.AddAdditionalCapability("app", calculatorAppId);
options.AddAdditionalCapability("deviceName", "WindowsPC");
this.CalculatorSession = new WindowsDriver<WindowsElement>(this.windowsDriverService.ServiceUrl, options);
this.CalculatorSession.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);
Assert.IsNotNull(this.CalculatorSession);
}
}
public void TearDownCalculatorSession()
{
// Close the application and delete the session
if (this.CalculatorSession != null)
{
this.CalculatorSession.Quit();
this.CalculatorSession = null;
}
if (this.windowsDriverService != null)
{
this.windowsDriverService.Dispose();
this.windowsDriverService = null;
}
}
}
}

View File

@@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using OpenQA.Selenium;
using OpenQA.Selenium.Appium.Windows;
namespace CalculatorUITestFramework
{
public static class WindowsDriverExtensions
{
/// <summary>
/// Wraps the WindowsDriver.FindElementByAccessibilityId and adds retry logic for when the element cannot be found due to WinAppDriver losing the window.
/// If FindElementByAccessibilityId fails for a different reason rethrow the error.
/// </summary>
public static WindowsElement TryFindElementByAccessibilityId(this WindowsDriver<WindowsElement> driver, string id)
{
try
{
return driver.FindElementByAccessibilityId(id);
}
catch (WebDriverException ex)
{
if (ex.Message.Contains("Currently selected window has been closed"))
{
driver.SwitchToCurrentWindowHandle();
return driver.FindElementByAccessibilityId(id);
}
throw;
}
}
/// <summary>
/// Wraps the WindowsDriver.FindElementByClassName and adds retry logic for when the element cannot be found due to WinAppDriver losing the window.
/// If FindElementByAccessibilityId fails for a different reason rethrow the error.
/// </summary>
public static WindowsElement TryFindElementByClassName(this WindowsDriver<WindowsElement> driver, string name)
{
try
{
return driver.FindElementByClassName(name);
}
catch (WebDriverException ex)
{
if (ex.Message.Contains("Currently selected window has been closed"))
{
driver.SwitchToCurrentWindowHandle();
return driver.FindElementByClassName(name);
}
throw;
}
}
/// <summary>
/// Gets the window handles for the current CalculatorSession and switches to the first one.
/// </summary>
public static void SwitchToCurrentWindowHandle(this WindowsDriver<WindowsElement> driver)
{
// Identify the current window handle. You can check through inspect.exe which window this is.
var currentWindowHandle = driver.CurrentWindowHandle;
// Return all window handles associated with this process/application.
// At this point hopefully you have one to pick from. Otherwise you can
// simply iterate through them to identify the one you want.
var allWindowHandles = driver.WindowHandles;
// Assuming you only have only one window entry in allWindowHandles and it is in fact the correct one,
// switch the session to that window as follows. You can repeat this logic with any top window with the same
// process id (any entry of allWindowHandles)
driver.SwitchTo().Window(allWindowHandles[0]);
}
}
}

View File

@@ -0,0 +1,191 @@
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//See the NOTICE file distributed with this work for additional
//information regarding copyright ownership.
//You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
//Portions Copyright(c) Microsoft Corporation
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Runtime.CompilerServices;
namespace CalculatorUITestFramework
{
public class WindowsDriverLocalService : IDisposable
{
private FileInfo FileName;
private string Arguments;
private IPAddress IP;
private int Port;
private TimeSpan InitializationTimeout;
private Process Service;
public event DataReceivedEventHandler OutputDataReceived;
internal WindowsDriverLocalService(
FileInfo fileName,
string arguments,
IPAddress ip,
int port,
TimeSpan initializationTimeout)
{
this.FileName = fileName;
this.Arguments = arguments;
this.IP = ip;
this.Port = port;
this.InitializationTimeout = initializationTimeout;
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void Start()
{
if (this.IsRunning)
{
return;
}
this.Service = new Process();
this.Service.StartInfo.FileName = FileName.FullName;
this.Service.StartInfo.Arguments = Arguments;
this.Service.StartInfo.UseShellExecute = false;
this.Service.StartInfo.CreateNoWindow = true;
this.Service.StartInfo.RedirectStandardOutput = true;
this.Service.OutputDataReceived += (sender, e) => OutputDataReceived?.Invoke(this, e);
bool isLaunched = false;
string msgTxt =
$"The local WinAppDriver server has not been started: {this.FileName.FullName} Arguments: {this.Arguments}. " +
"\n";
try
{
Service.Start();
Service.BeginOutputReadLine();
}
catch (Exception e)
{
DestroyProcess();
throw new Exception(msgTxt, e);
}
isLaunched = Ping();
if (!isLaunched)
{
DestroyProcess();
throw new Exception(
msgTxt +
$"Time {InitializationTimeout.TotalMilliseconds} ms for the service starting has been expired!");
}
}
public bool IsRunning
{
get
{
if (this.Service == null)
{
return false;
}
try
{
var pid = this.Service.Id;
}
catch (Exception)
{
return false;
}
return Ping();
}
}
public void Dispose()
{
DestroyProcess();
GC.SuppressFinalize(this);
}
public Uri ServiceUrl
{
// Note: append /wd/hub to the URL if you're directing the test at Appium
get { return new Uri($"http://{this.IP.ToString()}:{Convert.ToString(this.Port)}"); }
}
private void DestroyProcess()
{
if (this.Service == null)
{
return;
}
try
{
this.Service.Kill();
}
catch (Exception)
{
}
finally
{
this.Service.Close();
}
}
private bool Ping()
{
bool pinged = false;
Uri status;
Uri service = this.ServiceUrl;
if (service.IsLoopback)
{
status = new Uri("http://localhost:" + Convert.ToString(this.Port) + "/status");
}
else
{
status = new Uri(service.ToString() + "/status");
}
DateTime endTime = DateTime.Now.Add(this.InitializationTimeout);
while (!pinged & DateTime.Now < endTime)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(status);
HttpWebResponse response = null;
try
{
using (response = (HttpWebResponse)request.GetResponse())
{
pinged = true;
}
}
catch (Exception)
{
pinged = false;
}
finally
{
if (response != null)
{
response.Close();
}
}
}
return pinged;
}
}
}

View File

@@ -0,0 +1,103 @@
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//See the NOTICE file distributed with this work for additional
//information regarding copyright ownership.
//You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
//Portions Copyright(c) Microsoft Corporation
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
namespace CalculatorUITestFramework
{
public class WindowsDriverServiceBuilder
{
private string IpAddress = "127.0.0.1";
private int Port = 4723;
private TimeSpan StartUpTimeout = new TimeSpan(0, 2, 0);
private FileInfo FileInfo;
public WindowsDriverLocalService Build()
{
if (this.FileInfo == null)
{
this.FileInfo = new FileInfo(@"c:\Program Files (x86)\Windows Application Driver\winappdriver.exe");
}
return new WindowsDriverLocalService(this.FileInfo, string.Empty, IPAddress.Parse(this.IpAddress), this.Port, this.StartUpTimeout);
}
public WindowsDriverServiceBuilder WithFileInfo(FileInfo fileInfo)
{
if (fileInfo == null)
{
throw new ArgumentNullException("FileInfo should not be NULL");
}
this.FileInfo = fileInfo;
return this;
}
public WindowsDriverServiceBuilder WithStartUpTimeOut(TimeSpan startUpTimeout)
{
if (startUpTimeout == null)
{
throw new ArgumentNullException("A startup timeout should not be NULL");
}
this.StartUpTimeout = startUpTimeout;
return this;
}
public WindowsDriverServiceBuilder WithIPAddress(string ipAddress)
{
this.IpAddress = ipAddress;
return this;
}
public WindowsDriverServiceBuilder UsingPort(int port)
{
if (port < 0)
{
throw new ArgumentException("The port parameter should not be negative");
}
if (port == 0)
{
return UsingAnyFreePort();
}
this.Port = port;
return this;
}
public WindowsDriverServiceBuilder UsingAnyFreePort()
{
Socket sock = null;
try
{
sock = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
sock.Bind(new IPEndPoint(IPAddress.Any, 0));
this.Port = ((IPEndPoint)sock.LocalEndPoint).Port;
return this;
}
finally
{
if (sock != null)
{
sock.Dispose();
}
}
}
}
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting.Logging;
using OpenQA.Selenium.Appium.Windows;
using System;
using System.Diagnostics;
using System.Threading;
namespace CalculatorUITestFramework
{
public static class WindowsElementExtensions
{
/// <summary>
/// Waits for an element to be displayed until the timeout is reached.
/// </summary>
/// <param name="element">WindowsElement in the Calculator application.</param>
/// <param name="timeout">Timeout in ms.</param>
public static void WaitForDisplayed(this WindowsElement element, int timeout = 2000)
{
Stopwatch timer = new Stopwatch();
timer.Reset();
timer.Start();
while (timer.ElapsedMilliseconds < timeout)
{
if (element.Displayed)
{
timer.Stop();
return;
}
Logger.LogMessage("Waiting for 10ms in WaitForDisplayed");
Thread.Sleep(10);
}
timer.Stop();
Assert.Fail(String.Format("{0} was not displayed in {1} ms", element, timeout));
}
}
}

View File

@@ -1,44 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;
using System;
namespace CalculatorUITests
{
public class CalculatorSession
{
// Note: append /wd/hub to the URL if you're directing the test at Appium
private const string WindowsApplicationDriverUrl = "http://127.0.0.1:4723";
private const string CalculatorAppId = "Microsoft.WindowsCalculator.Dev_8wekyb3d8bbwe!App";
protected static WindowsDriver<WindowsElement> session;
public static void Setup(TestContext context)
{
// Launch Calculator application if it is not yet launched
if (session == null)
{
// Create a new session to bring up an instance of the Calculator application
// Note: Multiple calculator windows (instances) share the same process Id
var options = new AppiumOptions();
options.AddAdditionalCapability("app", CalculatorAppId);
options.AddAdditionalCapability("deviceName", "WindowsPC");
session = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), options);
session.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(180);
Assert.IsNotNull(session);
}
}
public static void TearDown()
{
// Close the application and delete the session
if (session != null)
{
session.Quit();
session = null;
}
}
}
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
@@ -13,6 +13,10 @@
<PackageReference Include="Appium.WebDriver" Version="4.0.0.6-beta" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CalculatorUITestFramework\CalculatorUITestFramework.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="CalculatorUITests.runsettings">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

View File

@@ -1,112 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium.Appium.Windows;
using System.Threading;
using System;
namespace CalculatorUITests
{
[TestClass]
public class StandardModeTests : CalculatorSession
{
private static WindowsElement header;
private static WindowsElement calculatorResult;
[TestMethod]
public void Addition()
{
// Find the buttons by their names and click them in sequence to perform 1 + 7 = 8
session.FindElementByName("One").Click();
session.FindElementByName("Plus").Click();
session.FindElementByName("Seven").Click();
session.FindElementByName("Equals").Click();
Assert.AreEqual("8", GetCalculatorResultText());
}
[TestMethod]
public void Division()
{
// Find the buttons by their accessibility ids and click them in sequence to perform 88 / 11 = 8
session.FindElementByAccessibilityId("num8Button").Click();
session.FindElementByAccessibilityId("num8Button").Click();
session.FindElementByAccessibilityId("divideButton").Click();
session.FindElementByAccessibilityId("num1Button").Click();
session.FindElementByAccessibilityId("num1Button").Click();
session.FindElementByAccessibilityId("equalButton").Click();
Assert.AreEqual("8", GetCalculatorResultText());
}
[TestMethod]
public void Multiplication()
{
session.FindElementByAccessibilityId("num9Button").Click();
session.FindElementByAccessibilityId("multiplyButton").Click();
session.FindElementByAccessibilityId("num9Button").Click();
session.FindElementByAccessibilityId("equalButton").Click();
Assert.AreEqual("81", GetCalculatorResultText());
}
[TestMethod]
public void Subtraction()
{
// Find the buttons by their accessibility ids using XPath and click them in sequence to perform 9 - 1 = 8
session.FindElementByAccessibilityId("num9Button").Click();
session.FindElementByAccessibilityId("minusButton").Click();
session.FindElementByAccessibilityId("num1Button").Click();
session.FindElementByAccessibilityId("equalButton").Click();
Assert.AreEqual("8", GetCalculatorResultText());
}
[ClassInitialize]
public static void ClassInitialize(TestContext context)
{
// Create session to launch a Calculator window
Setup(context);
// Identify calculator mode by locating the header
try
{
header = session.FindElementByAccessibilityId("Header");
}
catch
{
header = session.FindElementByAccessibilityId("ContentPresenter");
}
// Ensure that calculator is in standard mode
if (!header.Text.Equals("Standard", StringComparison.OrdinalIgnoreCase))
{
session.FindElementByAccessibilityId("TogglePaneButton").Click();
Thread.Sleep(TimeSpan.FromSeconds(1));
var splitViewPane = session.FindElementByClassName("SplitViewPane");
splitViewPane.FindElementByName("Standard Calculator").Click();
Thread.Sleep(TimeSpan.FromSeconds(1));
Assert.IsTrue(header.Text.Equals("Standard", StringComparison.OrdinalIgnoreCase));
}
// Locate the calculatorResult element
calculatorResult = session.FindElementByAccessibilityId("CalculatorResults");
Assert.IsNotNull(calculatorResult);
}
[ClassCleanup]
public static void ClassCleanup()
{
TearDown();
}
[TestInitialize]
public void Clear()
{
session.FindElementByName("Clear").Click();
Assert.AreEqual("0", GetCalculatorResultText());
}
private string GetCalculatorResultText()
{
return calculatorResult.Text.Replace("Display is", string.Empty).Trim();
}
}
}

View File

@@ -0,0 +1,323 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using CalculatorUITestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
namespace CalculatorUITests
{
[TestClass]
public class StandardModeFunctionalTests
{
private static StandardCalculatorPage page = new StandardCalculatorPage();
/// <summary>
/// Initializes the WinAppDriver web driver session.
/// </summary>
/// <param name="context"></param>
[ClassInitialize]
public static void ClassInitialize(TestContext context)
{
// Create session to launch a Calculator window
WinAppDriver.Instance.SetupCalculatorSession(context);
// Ensure that calculator is in standard mode
page.NavigateToStandardCalculator();
}
/// <summary>
/// Closes the app and WinAppDriver web driver session.
/// </summary>
[ClassCleanup]
public static void ClassCleanup()
{
// Tear down Calculator session.
WinAppDriver.Instance.TearDownCalculatorSession();
}
/// <summary>
/// Ensures the calculator is in a cleared state
/// </summary>
[TestInitialize]
public void TestInit()
{
if ("0" != page.GetCalculatorResultText())
{
page.ClearAll();
}
}
[TestCleanup]
public void TestCleanup()
{
page.ClearAll();
}
#region Smoke Tests
[TestMethod]
public void SmokeTest_Add()
{
page.StandardOperators.NumberPad.Input(3.5);
page.StandardOperators.PlusButton.Click();
page.StandardOperators.NumberPad.Input(0.25);
page.StandardOperators.EqualButton.Click();
Assert.AreEqual("3.75", page.GetCalculatorResultText());
}
[TestMethod]
public void SmokeTest_Add_KeyboardInput()
{
page.Header.SendKeys("3+3=");
Assert.AreEqual("6", page.GetCalculatorResultText());
}
[TestMethod]
public void SmokeTest_Subtract()
{
page.StandardOperators.NumberPad.Input(4.3);
page.StandardOperators.MinusButton.Click();
page.StandardOperators.NumberPad.Input(2.6);
page.StandardOperators.EqualButton.Click();
Assert.AreEqual("1.7", page.GetCalculatorResultText());
}
[TestMethod]
public void SmokeTest_History()
{
page.StandardOperators.NumberPad.Input(-3);
page.StandardOperators.PlusButton.Click();
page.StandardOperators.NumberPad.Input(-2.6);
page.StandardOperators.EqualButton.Click();
page.StandardOperators.NumberPad.Input(2);
page.StandardOperators.MinusButton.Click();
page.StandardOperators.NumberPad.Input(3);
page.StandardOperators.EqualButton.Click();
page.StandardOperators.NumberPad.Input(1);
page.StandardOperators.MultiplyButton.Click();
page.StandardOperators.NumberPad.Input(3);
page.StandardOperators.EqualButton.Click();
var historyItems = page.HistoryPanel.GetAllHistoryListViewItems();
Assert.IsTrue(historyItems[0].Text.Equals("1 × 3 = 3", StringComparison.InvariantCultureIgnoreCase));
Assert.IsTrue(historyItems[1].Text.Equals("2 Minus ( 3 = Minus (1", StringComparison.InvariantCultureIgnoreCase));
Assert.IsTrue(historyItems[2].Text.Equals("-3 + -2.6 = Minus (5.6", StringComparison.InvariantCultureIgnoreCase));
}
[TestMethod]
public void SmokeTest_Memory()
{
page.StandardOperators.NumberPad.Input(3);
page.StandardOperators.PlusButton.Click();
page.StandardOperators.NumberPad.Input(0);
page.StandardOperators.EqualButton.Click();
page.MemoryPanel.MemButton.Click();
page.StandardOperators.NumberPad.Input(2);
page.StandardOperators.DivideButton.Click();
page.StandardOperators.NumberPad.Input(3);
page.StandardOperators.EqualButton.Click();
page.MemoryPanel.MemButton.Click();
page.StandardOperators.NumberPad.Input(7);
page.StandardOperators.MultiplyButton.Click();
page.StandardOperators.NumberPad.Input(9);
page.StandardOperators.EqualButton.Click();
page.MemoryPanel.MemButton.Click();
var memoryItems = page.MemoryPanel.GetAllMemoryListViewItems();
Assert.IsTrue(memoryItems[0].Text.Equals("63", StringComparison.InvariantCultureIgnoreCase));
Assert.IsTrue(memoryItems[1].Text.Equals("0.6666666666666667", StringComparison.InvariantCultureIgnoreCase));
Assert.IsTrue(memoryItems[2].Text.Equals("3", StringComparison.InvariantCultureIgnoreCase));
return;
}
#endregion
#region Basic Arthimetic Tests
[TestMethod]
public void Operator_Reciprocal()
{
page.StandardOperators.NumberPad.Input(4);
page.StandardOperators.InvertButton.Click();
Assert.AreEqual("0.25", page.GetCalculatorResultText());
}
[TestMethod]
public void Operator_Squared()
{
page.StandardOperators.NumberPad.Input(-15.5);
page.StandardOperators.XPower2Button.Click();
Assert.AreEqual("240.25", page.GetCalculatorResultText());
}
[TestMethod]
public void Operator_SquareRoot()
{
page.StandardOperators.NumberPad.Input(144);
page.StandardOperators.SquareRootButton.Click();
Assert.AreEqual("12", page.GetCalculatorResultText());
}
[TestMethod]
public void Operator_Cubed()
{
page.StandardOperators.NumberPad.Input(-3);
page.StandardOperators.XPower3Button.Click();
Assert.AreEqual("-27", page.GetCalculatorResultText());
}
[TestMethod]
public void Operator_Percent()
{
page.StandardOperators.NumberPad.Input(600);
page.StandardOperators.MultiplyButton.Click();
page.StandardOperators.NumberPad.Input(15);
page.StandardOperators.PercentButton.Click();
page.StandardOperators.EqualButton.Click();
Assert.AreEqual("90", page.GetCalculatorResultText());
}
[TestMethod]
public void Operator_Delete()
{
page.StandardOperators.NumberPad.Input(-12345);
page.StandardOperators.BackSpaceButton.Click();
Assert.AreEqual("-1,234", page.GetCalculatorResultText());
}
[TestMethod]
public void Operator_ClearAll()
{
page.StandardOperators.NumberPad.Input(12345);
page.StandardOperators.PlusButton.Click();
page.StandardOperators.NumberPad.Input(6789);
page.StandardOperators.ClearButton.Click();
Assert.AreEqual("0", page.GetCalculatorResultText());
}
[TestMethod]
public void Operator_ClearEntry()
{
page.StandardOperators.NumberPad.Input(-12345);
page.StandardOperators.MinusButton.Click();
page.StandardOperators.NumberPad.Input(5678);
page.StandardOperators.ClearEntryButton.Click();
Assert.AreEqual("0", page.GetCalculatorResultText());
}
#endregion
#region Keyboard Input Tests
[TestMethod]
public void KeyboardInput_Subtract()
{
page.Header.SendKeys("70090-890987=");
Assert.AreEqual("-820,897", page.GetCalculatorResultText());
}
[TestMethod]
public void KeyboardInput_Multiply()
{
page.Header.SendKeys("-15*3=");
Assert.AreEqual("-45", page.GetCalculatorResultText());
}
[TestMethod]
public void KeyboardInput_Divide()
{
page.Header.SendKeys("26/13=");
Assert.AreEqual("2", page.GetCalculatorResultText());
}
[TestMethod]
public void KeyboardInput_SquareRoot()
{
page.Header.SendKeys("100@");
Assert.AreEqual("10", page.GetCalculatorResultText());
}
[TestMethod]
public void KeyboardInput_Cubed()
{
page.Header.SendKeys("3#");
Assert.AreEqual("27", page.GetCalculatorResultText());
}
[TestMethod]
public void KeyboardInput_Percent()
{
page.Header.SendKeys("34+7%=");
Assert.AreEqual("36.38", page.GetCalculatorResultText());
}
#endregion
#region Memory Tests
[TestMethod]
public void Memory_AddTest()
{
page.StandardOperators.NumberPad.Input(12);
page.StandardOperators.DivideButton.Click();
page.StandardOperators.NumberPad.Input(3);
page.StandardOperators.EqualButton.Click();
page.MemoryPanel.MemButton.Click();
page.StandardOperators.NumberPad.Input(15);
page.MemoryPanel.MemPlus.Click();
var memoryItems = page.MemoryPanel.GetAllMemoryListViewItems();
Assert.IsTrue(memoryItems[0].Text.Equals("19", StringComparison.InvariantCultureIgnoreCase));
}
[TestMethod]
public void Memory_SubtractTest()
{
page.StandardOperators.NumberPad.Input(12);
page.StandardOperators.DivideButton.Click();
page.StandardOperators.NumberPad.Input(3);
page.StandardOperators.EqualButton.Click();
page.MemoryPanel.MemButton.Click();
page.StandardOperators.NumberPad.Input(3.3);
page.MemoryPanel.MemMinus.Click();
var memoryItems = page.MemoryPanel.GetAllMemoryListViewItems();
Assert.IsTrue(memoryItems[0].Text.Equals("0.7", StringComparison.InvariantCultureIgnoreCase));
}
[TestMethod]
public void Memory_RecallTest()
{
page.StandardOperators.NumberPad.Input(12);
page.StandardOperators.DivideButton.Click();
page.StandardOperators.NumberPad.Input(3);
page.StandardOperators.EqualButton.Click();
page.MemoryPanel.MemButton.Click();
page.StandardOperators.NumberPad.Input(3.3);
page.MemoryPanel.MemRecall.Click();
var memoryItems = page.MemoryPanel.GetAllMemoryListViewItems();
Assert.IsTrue(memoryItems[0].Text.Equals("4", StringComparison.InvariantCultureIgnoreCase));
}
[TestMethod]
public void Memory_ClearTest()
{
page.StandardOperators.NumberPad.Input(12);
page.StandardOperators.DivideButton.Click();
page.StandardOperators.NumberPad.Input(3);
page.StandardOperators.EqualButton.Click();
page.MemoryPanel.MemButton.Click();
page.StandardOperators.NumberPad.Input(3.3);
page.MemoryPanel.MemoryClear.Click();
page.MemoryPanel.OpenMemoryPanel();
Assert.IsNotNull(page.MemoryPanel.MemoryPaneEmptyLabel);
}
#endregion
}
}