ElementExtensions.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reactive.Disposables;
  5. using System.Runtime.InteropServices;
  6. using OpenQA.Selenium;
  7. using OpenQA.Selenium.Appium;
  8. using OpenQA.Selenium.Interactions;
  9. using Xunit;
  10. namespace Avalonia.IntegrationTests.Appium
  11. {
  12. internal static class ElementExtensions
  13. {
  14. public static IReadOnlyList<AppiumWebElement> GetChildren(this AppiumWebElement element) =>
  15. element.FindElementsByXPath("*/*");
  16. public static (AppiumWebElement close, AppiumWebElement minimize, AppiumWebElement maximize) GetChromeButtons(this AppiumWebElement window)
  17. {
  18. if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
  19. {
  20. var closeButton = window.FindElementByXPath("//XCUIElementTypeButton[1]");
  21. var fullscreenButton = window.FindElementByXPath("//XCUIElementTypeButton[2]");
  22. var minimizeButton = window.FindElementByXPath("//XCUIElementTypeButton[3]");
  23. return (closeButton, minimizeButton, fullscreenButton);
  24. }
  25. throw new NotSupportedException("GetChromeButtons not supported on this platform.");
  26. }
  27. public static string GetComboBoxValue(this AppiumWebElement element)
  28. {
  29. return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
  30. element.Text :
  31. element.GetAttribute("value");
  32. }
  33. public static string GetName(this AppiumWebElement element) => GetAttribute(element, "Name", "title");
  34. public static bool? GetIsChecked(this AppiumWebElement element) =>
  35. GetAttribute(element, "Toggle.ToggleState", "value") switch
  36. {
  37. "0" => false,
  38. "1" => true,
  39. "2" => null,
  40. _ => throw new ArgumentOutOfRangeException($"Unexpected IsChecked value.")
  41. };
  42. public static bool GetIsFocused(this AppiumWebElement element)
  43. {
  44. if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
  45. {
  46. var active = element.WrappedDriver.SwitchTo().ActiveElement() as AppiumWebElement;
  47. return element.Id == active?.Id;
  48. }
  49. else
  50. {
  51. // https://stackoverflow.com/questions/71807788/check-if-element-is-focused-in-appium
  52. throw new NotSupportedException("Couldn't work out how to check if an element is focused on mac.");
  53. }
  54. }
  55. /// <summary>
  56. /// Clicks a button which is expected to open a new window.
  57. /// </summary>
  58. /// <param name="element">The button to click.</param>
  59. /// <returns>
  60. /// An object which when disposed will cause the newly opened window to close.
  61. /// </returns>
  62. public static IDisposable OpenWindowWithClick(this AppiumWebElement element)
  63. {
  64. var session = element.WrappedDriver;
  65. if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
  66. {
  67. var oldHandle = session.CurrentWindowHandle;
  68. var oldHandles = session.WindowHandles.ToList();
  69. var oldChildWindows = session.FindElements(By.XPath("//Window"));
  70. element.Click();
  71. var newHandle = session.WindowHandles.Except(oldHandles).SingleOrDefault();
  72. if (newHandle is not null)
  73. {
  74. // A new top-level window was opened. We need to switch to it.
  75. session.SwitchTo().Window(newHandle);
  76. return Disposable.Create(() =>
  77. {
  78. session.Close();
  79. session.SwitchTo().Window(oldHandle);
  80. });
  81. }
  82. else
  83. {
  84. // If a new window handle hasn't been added to the session then it's likely
  85. // that a child window was opened. These don't appear in session.WindowHandles
  86. // so we have to use an XPath query to get hold of it.
  87. var newChildWindows = session.FindElements(By.XPath("//Window"));
  88. var childWindow = Assert.Single(newChildWindows.Except(oldChildWindows));
  89. return Disposable.Create(() =>
  90. {
  91. childWindow.SendKeys(Keys.Alt + Keys.F4 + Keys.Alt);
  92. });
  93. }
  94. }
  95. else
  96. {
  97. var oldWindows = session.FindElements(By.XPath("/XCUIElementTypeApplication/XCUIElementTypeWindow"));
  98. var oldWindowTitles = oldWindows.ToDictionary(x => x.Text);
  99. element.Click();
  100. var newWindows = session.FindElements(By.XPath("/XCUIElementTypeApplication/XCUIElementTypeWindow"));
  101. var newWindowTitles = newWindows.ToDictionary(x => x.Text);
  102. var newWindowTitle = Assert.Single(newWindowTitles.Keys.Except(oldWindowTitles.Keys));
  103. var newWindow = (AppiumWebElement)newWindowTitles[newWindowTitle];
  104. return Disposable.Create(() =>
  105. {
  106. // TODO: We should be able to use Cmd+W here but Avalonia apps don't seem to have this shortcut
  107. // set up by default.
  108. var (close, _, _) = newWindow.GetChromeButtons();
  109. close!.Click();
  110. });
  111. }
  112. }
  113. public static void SendClick(this AppiumWebElement element)
  114. {
  115. // The Click() method seems to correspond to accessibilityPerformPress on macOS but certain controls
  116. // such as list items don't support this action, so instead simulate a physical click as VoiceOver
  117. // does. On Windows, Click() seems to fail with the WindowState checkbox for some reason.
  118. new Actions(element.WrappedDriver).MoveToElement(element).Click().Perform();
  119. }
  120. public static void MovePointerOver(this AppiumWebElement element)
  121. {
  122. new Actions(element.WrappedDriver).MoveToElement(element).Perform();
  123. }
  124. public static string GetAttribute(AppiumWebElement element, string windows, string macOS)
  125. {
  126. return element.GetAttribute(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? windows : macOS);
  127. }
  128. }
  129. }