File: Services\RazorService.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.
 
namespace Microsoft.VisualStudioCode.Razor.IntegrationTests.Services;
 
/// <summary>
/// Services for Razor language server operations in integration tests.
/// </summary>
public class RazorService(IntegrationTestServices testServices) : ServiceBase(testServices)
{
    /// <summary>
    /// Waits for the Razor language server to be fully initialized by verifying semantic tokenization is working.
    /// This checks that Razor component tags are being colorized (shows "razorComponentElement" in token inspector).
    /// </summary>
    public async Task WaitForReadyAsync(TimeSpan? timeout = null)
    {
        timeout ??= TimeSpan.FromSeconds(60);
        var attempt = 0;
 
        TestServices.Logger.Log("Waiting for Razor language server to be ready (checking semantic tokens)...");
 
        await Helper.WaitForConditionAsync(
            async () =>
            {
                attempt++;
                TestServices.Logger.Log($"Razor ready check attempt {attempt}...");
 
                try
                {
                    // Open Home.razor which contains <PageTitle> component
                    await TestServices.Editor.OpenFileAsync("Components/Pages/Home.razor");
 
                    // Navigate to PageTitle - it's typically on line 3
                    // Home.razor usually has: @page "/" then <PageTitle>Home</PageTitle>
                    await TestServices.Editor.GoToWordAsync("PageTitle", selectWord: false);
 
                    // Run "Developer: Inspect Editor Tokens and Scopes" command
                    await TestServices.Editor.ExecuteCommandAsync("Developer: Inspect Editor Tokens and Scopes");
 
                    // Wait for the token inspector popup to appear and contain razorComponentElement
                    var hasRazorToken = true;
                    try
                    {
                        await Helper.WaitForConditionAsync(CheckForRazorTokenAsync, TimeSpan.FromSeconds(3));
                    }
                    catch (TimeoutException)
                    {
                        // Token not found within timeout
                        hasRazorToken = false;
                        TestServices.Logger.Log("Token inspector did not show razorComponentElement");
                    }
 
                    // Close the token inspector by pressing Escape
                    await TestServices.Input.PressAsync(SpecialKey.Escape);
 
                    // Close the file and wait for tab to close
                    await TestServices.Input.PressWithPrimaryModifierAsync('w');
                    await Helper.WaitForConditionAsync(
                        async () =>
                        {
                            var fileName = await TestServices.Editor.GetCurrentFileNameAsync();
                            return fileName == null || !fileName.Contains("Home.razor", StringComparison.OrdinalIgnoreCase);
                        },
                        TimeSpan.FromSeconds(2));
 
                    if (hasRazorToken)
                    {
                        TestServices.Logger.Log($"Razor language server is ready - semantic tokens verified (attempt {attempt})");
                        return true;
                    }
 
                    TestServices.Logger.Log($"Razor tokens not yet available (attempt {attempt}), retrying...");
 
                    // Wait before next attempt
                    await Task.Delay(1000);
                    return false;
                }
                catch (Exception ex)
                {
                    TestServices.Logger.Log($"Razor ready check attempt {attempt} failed: {ex.Message}");
 
                    // Try to close any open file/dialog before retrying
                    try
                    {
                        await TestServices.Input.PressAsync(SpecialKey.Escape);
                        await TestServices.Input.PressWithPrimaryModifierAsync('w');
                    }
                    catch
                    {
                        // Ignore cleanup errors
                    }
 
                    await Task.Delay(1000);
                    return false;
                }
            },
            timeout.Value,
            initialDelayMs: 0); // No initial delay - the loop body handles delays
    }
 
    /// <summary>
    /// Checks if the token inspector popup contains "razorComponentElement".
    /// </summary>
    private async Task<bool> CheckForRazorTokenAsync()
    {
        // The token inspector shows in a hover-like widget
        // Look for the content that contains token scope information
        var tokenContent = await TestServices.Playwright.Page.EvaluateAsync<string?>(@"
            (() => {
                // The token inspector typically uses a hover widget
                // Try multiple selectors to find it
                const selectors = [
                    '.monaco-hover',
                    '.monaco-hover-content', 
                    '.editor-hover-content',
                    '.hover-row',
                    '.hover-contents',
                    '[class*=""hover""]'
                ];
                
                for (const selector of selectors) {
                    const elements = document.querySelectorAll(selector);
                    for (const el of elements) {
                        const text = el.textContent || '';
                        // Look for razorComponentElement anywhere in the text
                        if (text.includes('razorComponentElement')) {
                            return text;
                        }
                    }
                }
                
                // Fallback: search all visible elements for razorComponentElement
                const allElements = document.querySelectorAll('*');
                for (const el of allElements) {
                    const text = el.textContent || '';
                    if (text.includes('razorComponentElement')) {
                        return text.substring(0, 1000);
                    }
                }
                
                return null;
            })()
        ");
 
        if (string.IsNullOrEmpty(tokenContent))
        {
            TestServices.Logger.Log("Token inspector content not found or no razorComponentElement");
            return false;
        }
 
        TestServices.Logger.Log($"Found razorComponentElement in token inspector");
        return true;
    }
}