mirror of
https://github.com/Microsoft/calculator.git
synced 2025-08-23 06:25:19 -07:00
Add a robust way to start/stop the WinAppDriverService
* Add robust way of starting and stopping service
This commit is contained in:
parent
ea64f354ce
commit
45db18242f
4 changed files with 297 additions and 14 deletions
|
@ -15,6 +15,10 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- checkout: none
|
- checkout: none
|
||||||
|
|
||||||
|
- powershell: Set-DisplayResolution -Width 1920 -Height 1080 -Force
|
||||||
|
displayName: Set resolution to 1920x1080
|
||||||
|
continueOnError: true
|
||||||
|
|
||||||
- task: DownloadBuildArtifacts@0
|
- task: DownloadBuildArtifacts@0
|
||||||
displayName: Download AppxBundle and CalculatorUITests
|
displayName: Download AppxBundle and CalculatorUITests
|
||||||
inputs:
|
inputs:
|
||||||
|
@ -35,11 +39,6 @@ jobs:
|
||||||
filePath: $(Build.ArtifactStagingDirectory)/drop/Release/${{ parameters.platform }}/Calculator/AppPackages/Calculator_$(Build.BuildNumber)_Test/Add-AppDevPackage.ps1
|
filePath: $(Build.ArtifactStagingDirectory)/drop/Release/${{ parameters.platform }}/Calculator/AppPackages/Calculator_$(Build.BuildNumber)_Test/Add-AppDevPackage.ps1
|
||||||
arguments: -Force
|
arguments: -Force
|
||||||
|
|
||||||
- task: WinAppDriver.winappdriver-pipelines-task.winappdriver-pipelines-task.Windows Application Driver@0
|
|
||||||
displayName: 'WinAppDriver - Start'
|
|
||||||
inputs:
|
|
||||||
AgentResolution: 1080p
|
|
||||||
|
|
||||||
- task: VSTest@2
|
- task: VSTest@2
|
||||||
displayName: Run CalculatorUITests
|
displayName: Run CalculatorUITests
|
||||||
inputs:
|
inputs:
|
||||||
|
@ -48,8 +47,3 @@ jobs:
|
||||||
runSettingsFile: $(Build.ArtifactStagingDirectory)/drop/Release/${{ parameters.platform }}/publish/CalculatorUITests.runsettings
|
runSettingsFile: $(Build.ArtifactStagingDirectory)/drop/Release/${{ parameters.platform }}/publish/CalculatorUITests.runsettings
|
||||||
platform: ${{ parameters.platform }}
|
platform: ${{ parameters.platform }}
|
||||||
configuration: Release
|
configuration: Release
|
||||||
|
|
||||||
- task: WinAppDriver.winappdriver-pipelines-task.winappdriver-pipelines-task.Windows Application Driver@0
|
|
||||||
displayName: 'WinAppDriver - Stop'
|
|
||||||
inputs:
|
|
||||||
OperationType: Stop
|
|
|
@ -4,14 +4,14 @@ using OpenQA.Selenium.Appium;
|
||||||
using OpenQA.Selenium.Appium.Windows;
|
using OpenQA.Selenium.Appium.Windows;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace CalculatorUITestFramework
|
namespace CalculatorUITestFramework
|
||||||
{
|
{
|
||||||
public sealed class WinAppDriver
|
public sealed class WinAppDriver
|
||||||
{
|
{
|
||||||
// Note: append /wd/hub to the URL if you're directing the test at Appium
|
private WindowsDriverLocalService windowsDriverService = null;
|
||||||
private const string windowsApplicationDriverUrl = "http://127.0.0.1:4723";
|
|
||||||
private const string calculatorAppId = "Microsoft.WindowsCalculator.Dev_8wekyb3d8bbwe!App";
|
private const string calculatorAppId = "Microsoft.WindowsCalculator.Dev_8wekyb3d8bbwe!App";
|
||||||
private static WinAppDriver instance = null;
|
private static WinAppDriver instance = null;
|
||||||
public static WinAppDriver Instance
|
public static WinAppDriver Instance
|
||||||
|
@ -36,6 +36,18 @@ namespace CalculatorUITestFramework
|
||||||
|
|
||||||
public void SetupCalculatorSession(TestContext context)
|
public void SetupCalculatorSession(TestContext context)
|
||||||
{
|
{
|
||||||
|
windowsDriverService = new WindowsDriverServiceBuilder().Build();
|
||||||
|
|
||||||
|
windowsDriverService.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
|
||||||
|
{
|
||||||
|
if (!String.IsNullOrEmpty(e.Data))
|
||||||
|
{
|
||||||
|
Console.WriteLine(e.Data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
windowsDriverService.Start();
|
||||||
|
|
||||||
// Launch Calculator application if it is not yet launched
|
// Launch Calculator application if it is not yet launched
|
||||||
if (CalculatorSession == null)
|
if (CalculatorSession == null)
|
||||||
{
|
{
|
||||||
|
@ -44,7 +56,7 @@ namespace CalculatorUITestFramework
|
||||||
var options = new AppiumOptions();
|
var options = new AppiumOptions();
|
||||||
options.AddAdditionalCapability("app", calculatorAppId);
|
options.AddAdditionalCapability("app", calculatorAppId);
|
||||||
options.AddAdditionalCapability("deviceName", "WindowsPC");
|
options.AddAdditionalCapability("deviceName", "WindowsPC");
|
||||||
CalculatorSession = new WindowsDriver<WindowsElement>(new Uri(windowsApplicationDriverUrl), options);
|
CalculatorSession = new WindowsDriver<WindowsElement>(windowsDriverService.ServiceUrl, options);
|
||||||
CalculatorSession.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);
|
CalculatorSession.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);
|
||||||
Assert.IsNotNull(CalculatorSession);
|
Assert.IsNotNull(CalculatorSession);
|
||||||
}
|
}
|
||||||
|
@ -58,6 +70,12 @@ namespace CalculatorUITestFramework
|
||||||
CalculatorSession.Quit();
|
CalculatorSession.Quit();
|
||||||
CalculatorSession = null;
|
CalculatorSession = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (windowsDriverService != null)
|
||||||
|
{
|
||||||
|
windowsDriverService.Dispose();
|
||||||
|
windowsDriverService = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
178
src/CalculatorUITestFramework/WindowsDriverLocalService.cs
Normal file
178
src/CalculatorUITestFramework/WindowsDriverLocalService.cs
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
using OpenQA.Selenium.Appium.Service;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
FileName = fileName;
|
||||||
|
Arguments = arguments;
|
||||||
|
IP = ip;
|
||||||
|
Port = port;
|
||||||
|
InitializationTimeout = initializationTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
if (IsRunning)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Service = new Process();
|
||||||
|
Service.StartInfo.FileName = FileName.FullName;
|
||||||
|
Service.StartInfo.Arguments = Arguments;
|
||||||
|
Service.StartInfo.UseShellExecute = false;
|
||||||
|
Service.StartInfo.CreateNoWindow = true;
|
||||||
|
|
||||||
|
Service.StartInfo.RedirectStandardOutput = true;
|
||||||
|
Service.OutputDataReceived += (sender, e) => OutputDataReceived?.Invoke(this, e);
|
||||||
|
|
||||||
|
bool isLaunched = false;
|
||||||
|
string msgTxt =
|
||||||
|
$"The local WinAppDriver server has not been started: {FileName.FullName} Arguments: {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 (Service == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var pid = 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://{IP.ToString()}:{Convert.ToString(Port)}"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DestroyProcess()
|
||||||
|
{
|
||||||
|
if (Service == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Service.Kill();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Service.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Ping()
|
||||||
|
{
|
||||||
|
bool pinged = false;
|
||||||
|
|
||||||
|
Uri status;
|
||||||
|
|
||||||
|
Uri service = ServiceUrl;
|
||||||
|
if (service.IsLoopback)
|
||||||
|
{
|
||||||
|
status = new Uri("http://localhost:" + Convert.ToString(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
93
src/CalculatorUITestFramework/WindowsDriverServiceBuilder.cs
Normal file
93
src/CalculatorUITestFramework/WindowsDriverServiceBuilder.cs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
using OpenQA.Selenium;
|
||||||
|
using OpenQA.Selenium.Appium.Service;
|
||||||
|
using OpenQA.Selenium.Appium.Service.Options;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
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 (FileInfo == null)
|
||||||
|
{
|
||||||
|
FileInfo = new FileInfo(@"c:\Program Files (x86)\Windows Application Driver\winappdriver.exe");
|
||||||
|
}
|
||||||
|
return new WindowsDriverLocalService(FileInfo, string.Empty, IPAddress.Parse(this.IpAddress), this.Port, StartUpTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WindowsDriverServiceBuilder WithFileInfo(FileInfo fileInfo)
|
||||||
|
{
|
||||||
|
if (fileInfo == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("FileInfo should not be NULL");
|
||||||
|
}
|
||||||
|
FileInfo = fileInfo;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WindowsDriverServiceBuilder WithStartUpTimeOut(TimeSpan startUpTimeout)
|
||||||
|
{
|
||||||
|
if (startUpTimeout == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("A startup timeout should not be NULL");
|
||||||
|
}
|
||||||
|
StartUpTimeout = startUpTimeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WindowsDriverServiceBuilder WithIPAddress(string ipAddress)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
Port = ((IPEndPoint)sock.LocalEndPoint).Port;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (sock != null)
|
||||||
|
{
|
||||||
|
sock.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue