ElementExtensions.cs 7.3 KB

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