Skip to content

Evict unavailable elements and improve element compare performance #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/FlaUI.WebDriver.UITests/FindElementsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,23 @@ public void FindElements_InOtherWindow_ReturnsEmptyList()
Assert.That(elementsInNewWindow, Has.Count.EqualTo(1));
}

[Test]
public void FindElements_AfterPreviousKnownElementUnavailable_DoesNotThrow()
{
var driverOptions = FlaUIDriverOptions.TestApp();
using var driver = new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions);
var initialWindowHandle = driver.CurrentWindowHandle;
OpenAndSwitchToAnotherWindow(driver);
var elementInNewWindow = driver.FindElement(ExtendedBy.AccessibilityId("Window1TextBox"));
// close to make the elementInNewWindow unavailable
driver.Close();
driver.SwitchTo().Window(initialWindowHandle);

var foundElements = driver.FindElements(ExtendedBy.AccessibilityId("TextBox"));

Assert.That(foundElements, Has.Count.EqualTo(1));
}

private static void OpenAndSwitchToAnotherWindow(RemoteWebDriver driver)
{
var initialWindowHandles = new[] { driver.CurrentWindowHandle };
Expand Down
1 change: 1 addition & 0 deletions src/FlaUI.WebDriver/ISessionRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public interface ISessionRepository
{
void Add(Session session);
void Delete(Session session);
List<Session> FindAll();
Session? FindById(string sessionId);
List<Session> FindTimedOut();
}
Expand Down
13 changes: 11 additions & 2 deletions src/FlaUI.WebDriver/KnownElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,22 @@ namespace FlaUI.WebDriver
{
public class KnownElement
{
public KnownElement(AutomationElement element)
public KnownElement(AutomationElement element, string? elementRuntimeId)
{
Element = element;
ElementRuntimeId = elementRuntimeId;
ElementReference = Guid.NewGuid().ToString();
}

public string ElementReference { get; set; }
public string ElementReference { get; }

/// <summary>
/// A temporarily unique ID, so cannot be used for identity over time, but can be used for improving performance of equality tests.
/// "The identifier is only guaranteed to be unique to the UI of the desktop on which it was generated. Identifiers can be reused over time."
/// </summary>
/// <seealso href="https://learn.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomationelement-getruntimeid"/>
public string? ElementRuntimeId { get; }

public AutomationElement Element { get; }
}
}
11 changes: 10 additions & 1 deletion src/FlaUI.WebDriver/KnownWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ namespace FlaUI.WebDriver
{
public class KnownWindow
{
public KnownWindow(Window window)
public KnownWindow(Window window, string? windowRuntimeId)
{
Window = window;
WindowRuntimeId = windowRuntimeId;
WindowHandle = Guid.NewGuid().ToString();
}
public string WindowHandle { get; set; }

/// <summary>
/// A temporarily unique ID, so cannot be used for identity over time, but can be used for improving performance of equality tests.
/// "The identifier is only guaranteed to be unique to the UI of the desktop on which it was generated. Identifiers can be reused over time."
/// </summary>
/// <seealso href="https://learn.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomationelement-getruntimeid"/>
public string? WindowRuntimeId { get; set; }

public Window Window { get; set; }
}
}
55 changes: 51 additions & 4 deletions src/FlaUI.WebDriver/Session.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using FlaUI.Core;
using FlaUI.Core.AutomationElements;
using FlaUI.UIA3;
using System.Runtime.InteropServices;

namespace FlaUI.WebDriver
{
Expand Down Expand Up @@ -77,10 +78,11 @@ public void SetLastCommandTimeToNow()

public KnownElement GetOrAddKnownElement(AutomationElement element)
{
var result = KnownElementsByElementReference.Values.FirstOrDefault(knownElement => knownElement.Element.Equals(element));
var elementRuntimeId = GetRuntimeId(element);
var result = KnownElementsByElementReference.Values.FirstOrDefault(knownElement => knownElement.ElementRuntimeId == elementRuntimeId && SafeElementEquals(knownElement.Element, element));
if (result == null)
{
result = new KnownElement(element);
result = new KnownElement(element, elementRuntimeId);
KnownElementsByElementReference.Add(result.ElementReference, result);
}
return result;
Expand All @@ -97,10 +99,11 @@ public KnownElement GetOrAddKnownElement(AutomationElement element)

public KnownWindow GetOrAddKnownWindow(Window window)
{
var result = KnownWindowsByWindowHandle.Values.FirstOrDefault(knownElement => knownElement.Window.Equals(window));
var windowRuntimeId = GetRuntimeId(window);
var result = KnownWindowsByWindowHandle.Values.FirstOrDefault(knownWindow => knownWindow.WindowRuntimeId == windowRuntimeId && SafeElementEquals(knownWindow.Window, window));
if (result == null)
{
result = new KnownWindow(window);
result = new KnownWindow(window, windowRuntimeId);
KnownWindowsByWindowHandle.Add(result.WindowHandle, result);
}
return result;
Expand All @@ -124,6 +127,26 @@ public void RemoveKnownWindow(Window window)
}
}

public void EvictUnavailableElements()
{
// Evict unavailable elements to prevent slowing down
var unavailableElements = KnownElementsByElementReference.Where(item => !item.Value.Element.IsAvailable).Select(item => item.Key).ToArray();
foreach (var unavailableElementKey in unavailableElements)
{
KnownElementsByElementReference.Remove(unavailableElementKey);
}
}

public void EvictUnavailableWindows()
{
// Evict unavailable windows to prevent slowing down
var unavailableWindows = KnownWindowsByWindowHandle.Where(item => !item.Value.Window.IsAvailable).Select(item => item.Key).ToArray();
foreach (var unavailableWindowKey in unavailableWindows)
{
KnownWindowsByWindowHandle.Remove(unavailableWindowKey);
}
}

public void Dispose()
{
if (IsAppOwnedBySession && App != null && !App.HasExited)
Expand All @@ -133,5 +156,29 @@ public void Dispose()
Automation.Dispose();
App?.Dispose();
}

private string? GetRuntimeId(AutomationElement element)
{
if (!element.Properties.RuntimeId.IsSupported)
{
return null;
}

return string.Join(",", element.Properties.RuntimeId.Value.Select(item => Convert.ToBase64String(BitConverter.GetBytes(item))));
}

private bool SafeElementEquals(AutomationElement element1, AutomationElement element2)
{
try
{
return element1.Equals(element2);
}
catch (COMException)
{
// May occur if the element is suddenly no longer available
return false;
}
}

}
}
40 changes: 28 additions & 12 deletions src/FlaUI.WebDriver/SessionCleanupService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,38 @@ private void DoWork(object? state)
scope.ServiceProvider
.GetRequiredService<ISessionRepository>();

var timedOutSessions = sessionRepository.FindTimedOut();
if(timedOutSessions.Count > 0)
{
_logger.LogInformation("Session cleanup service cleaning up {Count} sessions that did not receive commands in their specified new command timeout interval", timedOutSessions.Count);
RemoveTimedOutSessions(sessionRepository);

foreach (Session session in timedOutSessions)
{
sessionRepository.Delete(session);
session.Dispose();
}
}
else
EvictUnavailableElements(sessionRepository);
}
}

private void EvictUnavailableElements(ISessionRepository sessionRepository)
{
foreach(var session in sessionRepository.FindAll())
{
session.EvictUnavailableElements();
session.EvictUnavailableWindows();
}
}

private void RemoveTimedOutSessions(ISessionRepository sessionRepository)
{
var timedOutSessions = sessionRepository.FindTimedOut();
if (timedOutSessions.Count > 0)
{
_logger.LogInformation("Session cleanup service cleaning up {Count} sessions that did not receive commands in their specified new command timeout interval", timedOutSessions.Count);

foreach (Session session in timedOutSessions)
{
_logger.LogInformation("Session cleanup service did not find sessions to cleanup");
sessionRepository.Delete(session);
session.Dispose();
}
}
else
{
_logger.LogInformation("Session cleanup service did not find sessions to cleanup");
}
}

public Task StopAsync(CancellationToken stoppingToken)
Expand Down
5 changes: 5 additions & 0 deletions src/FlaUI.WebDriver/SessionRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,10 @@ public List<Session> FindTimedOut()
{
return Sessions.Where(session => session.IsTimedOut).ToList();
}

public List<Session> FindAll()
{
return new List<Session>(Sessions);
}
}
}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy