File: Services\CompletionServices.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 completion (IntelliSense) operations in integration tests.
/// </summary>
public class CompletionServices(IntegrationTestServices testServices) : ServiceBase(testServices)
{
 
    /// <summary>
    /// Triggers IntelliSense completion and waits for it to appear.
    /// </summary>
    /// <param name="waitForList">If true, waits for the completion list to appear.</param>
    /// <param name="timeout">Timeout for waiting. Uses LspTimeout if not specified.</param>
    public async Task<bool> TriggerAsync(bool waitForList = true, TimeSpan? timeout = null)
    {
        await TestServices.Input.PressWithPrimaryModifierAsync(SpecialKey.Space);
 
        if (waitForList)
        {
            return await WaitForListAsync(timeout);
        }
 
        return true;
    }
 
    /// <summary>
    /// Waits for the completion list to appear.
    /// </summary>
    public async Task<bool> WaitForListAsync(TimeSpan? timeout = null)
    {
        timeout ??= TestServices.Settings.LspTimeout;
 
        try
        {
            // The suggest widget has class "visible" when shown
            await TestServices.Playwright.Page.Locator(".suggest-widget.visible")
                .WaitForAsync(new LocatorWaitForOptions
                {
                    State = WaitForSelectorState.Visible,
                    Timeout = (float)timeout.Value.TotalMilliseconds
                });
 
            return true;
        }
        catch (TimeoutException)
        {
            return false;
        }
    }
 
    /// <summary>
    /// Gets the items in the completion list.
    /// </summary>
    public async Task<List<string>> GetItemsAsync()
    {
        var results = new List<string>();
 
        // Wait a moment for completion items to populate
        await WaitForListAsync();
 
        // Try to get items using JavaScript which can access more of the DOM
        var itemTexts = await TestServices.Playwright.Page.EvaluateAsync<string[]>(@"
            (() => {
                const widget = document.querySelector('.suggest-widget');
                if (!widget) return [];
                
                // Look for the list container - VS Code uses virtualized lists
                const listContainer = widget.querySelector('.monaco-list');
                if (!listContainer) return [];
                
                // Get visible row contents
                const rows = listContainer.querySelectorAll('.monaco-list-row');
                const items = [];
                
                for (const row of rows) {
                    // Try different ways to get the text
                    const label = row.querySelector('.monaco-icon-label-container');
                    if (label) {
                        items.push(label.textContent || '');
                        continue;
                    }
                    
                    const labelName = row.querySelector('.label-name');
                    if (labelName) {
                        items.push(labelName.textContent || '');
                        continue;
                    }
                    
                    // Fallback to row text
                    items.push(row.textContent || '');
                }
                
                return items;
            })()
        ") ?? [];
 
        if (itemTexts.Length > 0)
        {
            results.AddRange(itemTexts.Where(t => !string.IsNullOrWhiteSpace(t)));
            TestServices.Logger.Log($"Found {results.Count} completion items via JS");
            if (results.Count > 0)
            {
                TestServices.Logger.Log($"First few items: {string.Join(", ", results.Take(5))}");
            }
 
            return results;
        }
 
        // Debug: dump the full suggest widget structure
        var debugInfo = await TestServices.Playwright.Page.EvaluateAsync<string>(@"
            (() => {
                const widget = document.querySelector('.suggest-widget');
                if (!widget) return 'No suggest widget found';
                
                let info = `Widget classes: ${widget.className}. `;
                
                // Check for message element (means no suggestions)
                const message = widget.querySelector('.message');
                if (message) {
                    info += `Message: '${message.textContent}'. `;
                }
                
                const listElement = widget.querySelector('.monaco-list');
                if (listElement) {
                    const rows = listElement.querySelectorAll('.monaco-list-row');
                    info += `Rows: ${rows.length}. `;
                    if (rows.length > 0) {
                        info += `First row: '${rows[0].textContent?.substring(0, 50)}'. `;
                    }
                }
                
                return info;
            })()
        ");
        TestServices.Logger.Log($"Suggest widget debug: {debugInfo}");
 
        return results;
    }
}