File: Services\HoverServices.cs
Web Access
Project: src\src\Razor\src\Razor\test\Microsoft.VisualStudioCode.Razor.IntegrationTests\Microsoft.VisualStudioCode.Razor.IntegrationTests.csproj (Microsoft.VisualStudioCode.Razor.IntegrationTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.Playwright;
 
namespace Microsoft.VisualStudioCode.Razor.IntegrationTests.Services;
 
/// <summary>
/// Services for hover (Quick Info) operations in integration tests.
/// </summary>
public class HoverServices(IntegrationTestServices testServices) : ServiceBase(testServices)
{
    /// <summary>
    /// Triggers hover information at the current cursor position and waits for it to appear.
    /// </summary>
    /// <param name="waitForHover">If true, waits for the hover content to appear.</param>
    /// <param name="timeout">Timeout for waiting. Uses LspTimeout if not specified.</param>
    public async Task<bool> TriggerAsync(bool waitForHover = true, TimeSpan? timeout = null)
    {
        // Move mouse to the current cursor position and hover
        // Use First since there may be multiple cursor elements (e.g., in split editors or interactive window)
        var cursorLocator = TestServices.Playwright.Page.Locator(".cursor");
        if (await cursorLocator.CountAsync() == 0)
        {
            return false;
        }
 
        var box = await cursorLocator.First.BoundingBoxAsync();
        if (box == null)
        {
            return false;
        }
 
        await TestServices.Playwright.Page.Mouse.MoveAsync(box.X + (box.Width / 2), box.Y + (box.Height / 2));
 
        if (waitForHover)
        {
            return await WaitForAsync(timeout);
        }
 
        return true;
    }
 
    /// <summary>
    /// Waits for hover content to appear.
    /// </summary>
    public async Task<bool> WaitForAsync(TimeSpan? timeout = null)
    {
        timeout ??= TestServices.Settings.LspTimeout;
 
        try
        {
            // Use First since there may be multiple hover widgets
            await TestServices.Playwright.Page.Locator(".monaco-hover-content").First
                .WaitForAsync(new LocatorWaitForOptions
                {
                    State = WaitForSelectorState.Visible,
                    Timeout = (float)timeout.Value.TotalMilliseconds
                });
            return true;
        }
        catch (TimeoutException)
        {
            return false;
        }
    }
 
    /// <summary>
    /// Gets the hover content text.
    /// </summary>
    public async Task<string?> GetContentAsync()
    {
        var hoverLocator = TestServices.Playwright.Page.Locator(".monaco-hover-content");
        if (await hoverLocator.CountAsync() == 0)
        {
            return null;
        }
 
        return await hoverLocator.First.TextContentAsync();
    }
 
    /// <summary>
    /// Triggers hover and waits for the hover content text to appear.
    /// Waits for actual content to appear (not "Loading...").
    /// </summary>
    /// <returns>The hover content text, or null if hover failed to appear.</returns>
    public async Task<string?> WaitForContentAsync(TimeSpan? timeout = null)
    {
        timeout ??= TimeSpan.FromSeconds(10);
 
        var hasHover = await TriggerAsync();
        if (!hasHover)
        {
            return null;
        }
 
        // Wait for actual content, not "Loading..."
        string? content = null;
        try
        {
            await Helper.WaitForConditionAsync(
                async () =>
                {
                    content = await GetContentAsync();
                    return !string.IsNullOrEmpty(content) && !content.Equals("Loading...", StringComparison.OrdinalIgnoreCase);
                },
                timeout.Value);
        }
        catch (TimeoutException)
        {
            // Return whatever we have, even if it's still "Loading..."
        }
 
        return content ?? await GetContentAsync();
    }
}