-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
Copy pathElementExtensions.cs
253 lines (217 loc) · 10.9 KB
/
ElementExtensions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
using System.Threading;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Interactions;
using Xunit;
namespace Avalonia.IntegrationTests.Appium
{
public record WindowChrome(
AppiumWebElement? Close,
AppiumWebElement? Minimize,
AppiumWebElement? Maximize,
AppiumWebElement? FullScreen,
AppiumWebElement? TitleBar)
{
public bool IsAnyButtonEnabled => (TitleBar is null || TitleBar.Enabled) &&
(Close?.Enabled == true
|| Minimize?.Enabled == true
|| Maximize?.Enabled == true
|| FullScreen?.Enabled == true);
public int TitleBarHeight => TitleBar?.Size.Height ?? -1;
public int MaxButtonHeight =>
Math.Max(
Math.Max(Close?.Size.Height ?? -1, Minimize?.Size.Height ?? -1),
Math.Max(Maximize?.Size.Height ?? -1, FullScreen?.Size.Height ?? -1));
}
internal static class ElementExtensions
{
public static IReadOnlyList<AppiumWebElement> GetChildren(this AppiumWebElement element) =>
element.FindElementsByXPath("*/*");
public static WindowChrome GetSystemChromeButtons(this AppiumWebElement window)
{
if (OperatingSystem.IsMacOS())
{
var closeButton = window.FindElementsByAccessibilityId("_XCUI:CloseWindow").FirstOrDefault();
var fullscreenButton = window.FindElementsByAccessibilityId("_XCUI:FullScreenWindow").FirstOrDefault();
var minimizeButton = window.FindElementsByAccessibilityId("_XCUI:MinimizeWindow").FirstOrDefault();
var zoomButton = window.FindElementsByAccessibilityId("_XCUI:ZoomWindow").FirstOrDefault();
return new(closeButton, minimizeButton, zoomButton, fullscreenButton, null);
}
if (OperatingSystem.IsWindows())
{
var titlebar = window.FindElementsByTagName("TitleBar").FirstOrDefault();
var closeButton = titlebar?.FindElementByName("Close");
var minimizeButton = titlebar?.FindElementByName("Minimize");
var maximizeButton = titlebar?.FindElementByName("Maximize");
return new(closeButton, minimizeButton, maximizeButton, null, titlebar);
}
throw new NotSupportedException("GetChromeButtons not supported on this platform.");
}
public static WindowChrome GetClientChromeButtons(this AppiumWebElement window)
{
var titlebar = window.FindElementsByAccessibilityId("AvaloniaTitleBar")?.FirstOrDefault();
var closeButton = titlebar?.FindElementByName("Close");
var minimizeButton = titlebar?.FindElementByName("Minimize");
var maximizeButton = titlebar?.FindElementByName("Maximize");
return new(closeButton, minimizeButton, maximizeButton, null, titlebar);
}
public static string GetComboBoxValue(this AppiumWebElement element)
{
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
element.Text :
element.GetAttribute("value");
}
public static string GetName(this AppiumWebElement element) => GetAttribute(element, "Name", "title");
public static bool? GetIsChecked(this AppiumWebElement element) =>
GetAttribute(element, "Toggle.ToggleState", "value") switch
{
"0" => false,
"1" => true,
"2" => null,
_ => throw new ArgumentOutOfRangeException($"Unexpected IsChecked value.")
};
public static bool GetIsFocused(this AppiumWebElement element)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return element.GetAttribute("HasKeyboardFocus") == "True";
}
else
{
// https://stackoverflow.com/questions/71807788/check-if-element-is-focused-in-appium
throw new NotSupportedException("Couldn't work out how to check if an element is focused on mac.");
}
}
public static AppiumWebElement GetCurrentSingleWindow(this AppiumDriver session)
{
if (OperatingSystem.IsMacOS())
{
// The Avalonia a11y tree currently exposes two nested Window elements, this is a bug and should be fixed
// but in the meantime use the `parent::' selector to return the parent "real" window.
return session.FindElementByXPath(
$"XCUIElementTypeWindow//*/parent::XCUIElementTypeWindow");
}
else
{
return session.FindElementByXPath($"//Window");
}
}
public static AppiumWebElement GetWindowById(this AppiumDriver session, string identifier)
{
if (OperatingSystem.IsMacOS())
{
return session.FindElementByXPath(
$"XCUIElementTypeWindow[@identifier='{identifier}']");
}
else
{
return session.FindElementByXPath($"//Window[@AutomationId='{identifier}']");
}
}
/// <summary>
/// Clicks a button which is expected to open a new window.
/// </summary>
/// <param name="element">The button to click.</param>
/// <returns>
/// An object which when disposed will cause the newly opened window to close.
/// </returns>
public static IDisposable OpenWindowWithClick(this AppiumWebElement element, TimeSpan? delay = null)
{
var session = element.WrappedDriver;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var oldHandle = session.CurrentWindowHandle;
var oldHandles = session.WindowHandles.ToList();
var oldChildWindows = session.FindElements(By.XPath("//Window"));
element.Click();
if (delay is not null)
Thread.Sleep((int)delay.Value.TotalMilliseconds);
string? newHandle = null;
IWebElement? newChildWindow = null;
for (var i = 0; i < 10; ++i)
{
newHandle = session.WindowHandles.Except(oldHandles).SingleOrDefault();
if (newHandle is not null)
break;
// If a new window handle hasn't been added to the session then it's likely
// that a child window was opened. These don't appear in session.WindowHandles
// so we have to use an XPath query to get hold of it.
var newChildWindows = session.FindElements(By.XPath("//Window"));
newChildWindow = newChildWindows.Except(oldChildWindows).SingleOrDefault();
if (newChildWindow is not null)
break;
Thread.Sleep(100);
}
if (newHandle is not null)
{
// A new top-level window was opened. We need to switch to it.
session.SwitchTo().Window(newHandle);
return Disposable.Create(() =>
{
session.Close();
session.SwitchTo().Window(oldHandle);
});
}
if (newChildWindow is not null)
{
return Disposable.Create(() =>
{
newChildWindow.SendKeys(Keys.Alt + Keys.F4 + Keys.Alt);
});
}
Assert.Fail("Could not find the newly opened window");
return Disposable.Empty;
}
else
{
var oldWindows = session.FindElements(By.XPath("/XCUIElementTypeApplication/XCUIElementTypeWindow"));
var oldWindowTitles = oldWindows.ToDictionary(x => x.Text);
element.Click();
// Wait for animations to run.
Thread.Sleep(1000);
var newWindows = session.FindElements(By.XPath("/XCUIElementTypeApplication/XCUIElementTypeWindow"));
// Try to find the new window by looking for a window with a title that didn't exist before the button
// was clicked. Sometimes it seems that when a window becomes fullscreen, all other windows in the
// application lose their titles, so filter out windows with no title (this may have started happening
// with macOS 13.1?)
var newWindowTitles = newWindows
.Select(x => (x.Text, x))
.Where(x => !string.IsNullOrEmpty(x.Text))
.ToDictionary(x => x.Text, x => x.x);
var newWindowTitle = Assert.Single(newWindowTitles.Keys.Except(oldWindowTitles.Keys));
return Disposable.Create(() =>
{
// TODO: We should be able to use Cmd+W here but Avalonia apps don't seem to have this shortcut
// set up by default.
var windows = session.FindElements(By.XPath("/XCUIElementTypeApplication/XCUIElementTypeWindow"));
var text = windows.Select(x => x.Text).ToList();
var newWindow = session.FindElements(By.XPath("/XCUIElementTypeApplication/XCUIElementTypeWindow"))
.First(x => x.Text == newWindowTitle);
var close = ((AppiumWebElement)newWindow).FindElementByAccessibilityId("_XCUI:CloseWindow");
close!.Click();
Thread.Sleep(1000);
});
}
}
public static void SendClick(this AppiumWebElement element)
{
// The Click() method seems to correspond to accessibilityPerformPress on macOS but certain controls
// such as list items don't support this action, so instead simulate a physical click as VoiceOver
// does. On Windows, Click() seems to fail with the WindowState checkbox for some reason.
new Actions(element.WrappedDriver).MoveToElement(element).Click().Perform();
}
public static void MovePointerOver(this AppiumWebElement element)
{
new Actions(element.WrappedDriver).MoveToElement(element).Perform();
}
public static string GetAttribute(AppiumWebElement element, string windows, string macOS)
{
return element.GetAttribute(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? windows : macOS);
}
}
}