Add a robust way to start/stop the WinAppDriverService

* Add robust way of starting and stopping service
This commit is contained in:
Pepe Rivera 2019-05-20 16:27:01 -07:00 committed by GitHub
commit 45db18242f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 297 additions and 14 deletions

View file

@ -15,6 +15,10 @@ jobs:
steps:
- checkout: none
- powershell: Set-DisplayResolution -Width 1920 -Height 1080 -Force
displayName: Set resolution to 1920x1080
continueOnError: true
- task: DownloadBuildArtifacts@0
displayName: Download AppxBundle and CalculatorUITests
inputs:
@ -35,11 +39,6 @@ jobs:
filePath: $(Build.ArtifactStagingDirectory)/drop/Release/${{ parameters.platform }}/Calculator/AppPackages/Calculator_$(Build.BuildNumber)_Test/Add-AppDevPackage.ps1
arguments: -Force
- task: WinAppDriver.winappdriver-pipelines-task.winappdriver-pipelines-task.Windows Application Driver@0
displayName: 'WinAppDriver - Start'
inputs:
AgentResolution: 1080p
- task: VSTest@2
displayName: Run CalculatorUITests
inputs:
@ -47,9 +46,4 @@ jobs:
vsTestVersion: 16.0
runSettingsFile: $(Build.ArtifactStagingDirectory)/drop/Release/${{ parameters.platform }}/publish/CalculatorUITests.runsettings
platform: ${{ parameters.platform }}
configuration: Release
- task: WinAppDriver.winappdriver-pipelines-task.winappdriver-pipelines-task.Windows Application Driver@0
displayName: 'WinAppDriver - Stop'
inputs:
OperationType: Stop
configuration: Release

View file

@ -4,14 +4,14 @@ using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace CalculatorUITestFramework
{
public sealed class WinAppDriver
{
// 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 WindowsDriverLocalService windowsDriverService = null;
private const string calculatorAppId = "Microsoft.WindowsCalculator.Dev_8wekyb3d8bbwe!App";
private static WinAppDriver instance = null;
public static WinAppDriver Instance
@ -36,6 +36,18 @@ namespace CalculatorUITestFramework
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
if (CalculatorSession == null)
{
@ -44,7 +56,7 @@ namespace CalculatorUITestFramework
var options = new AppiumOptions();
options.AddAdditionalCapability("app", calculatorAppId);
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);
Assert.IsNotNull(CalculatorSession);
}
@ -58,6 +70,12 @@ namespace CalculatorUITestFramework
CalculatorSession.Quit();
CalculatorSession = null;
}
if (windowsDriverService != null)
{
windowsDriverService.Dispose();
windowsDriverService = null;
}
}

View 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;
}
}
}

View 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();
}
}
}
}
}