File: Services\VSCodeIntegrationTestBase.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 System.Runtime.CompilerServices;
using Xunit;
using Xunit.Abstractions;
 
namespace Microsoft.VisualStudioCode.Razor.IntegrationTests.Services;
 
/// <summary>
/// Base class for VS Code integration tests.
/// Manages the VS Code lifecycle - each test gets its own VS Code instance for isolation.
/// </summary>
public abstract class VSCodeIntegrationTestBase : IAsyncLifetime
{
    private readonly ITestOutputHelper _output;
 
    protected VSCodeIntegrationTestBase(ITestOutputHelper output)
    {
        _output = output;
    }
 
    /// <summary>
    /// Integration test services providing access to VS Code, Editor, and test helpers.
    /// </summary>
    protected IntegrationTestServices TestServices { get; private set; } = null!;
 
    /// <summary>
    /// Runs a test action and automatically takes a screenshot and collects logs if it fails.
    /// </summary>
    /// <param name="testAction">The test action to run.</param>
    /// <param name="testName">The test name (auto-populated by compiler).</param>
    protected async Task ScreenshotOnFailureAsync(Func<Task> testAction, [CallerMemberName] string testName = "")
    {
        try
        {
            await testAction();
        }
        catch (Exception ex)
        {
            // Take a screenshot and collect logs on failure
            TestServices.Logger.Log($"Test '{testName}' failed: {ex.Message}");
            await TestServices.Playwright.TakeScreenshotAsync($"FAILED_{testName}");
            TestServices.VSCode.CollectLogsOnFailure(testName);
            throw;
        }
    }
 
    public virtual async Task InitializeAsync()
    {
        // Create settings and test services
        var settings = TestSettings.CreateDefault();
        TestServices = new IntegrationTestServices(_output, settings);
 
        // Log environment info for debugging CI issues
        TestServices.Logger.Log($"Environment: DISPLAY={Environment.GetEnvironmentVariable("DISPLAY") ?? "(not set)"}");
        TestServices.Logger.Log($"OS: {Environment.OSVersion}");
        TestServices.Logger.Log($"Initialization timeout: {settings.InitializationTimeout}");
 
        // Clear VS Code logs before each test to ensure clean logs
        TestServices.VSCode.ClearLogs();
 
        // Run initialization with an overall timeout to prevent indefinite hangs
        using var cts = new CancellationTokenSource(settings.InitializationTimeout);
        var currentStep = "unknown";
 
        try
        {
            // Step 1: Ensure VS Code is installed with extensions
            currentStep = "Step 1/7: Ensuring VS Code is installed";
            TestServices.Logger.Log($"{currentStep}...");
            await TestServices.VSCode.EnsureInstalledAsync().WaitAsync(cts.Token);
            TestServices.Logger.Log($"{currentStep} - Complete");
 
            // Step 2: Create a test workspace
            currentStep = "Step 2/7: Creating test workspace";
            TestServices.Logger.Log($"{currentStep}...");
            await TestServices.Workspace.CreateAsync().WaitAsync(cts.Token);
            TestServices.Logger.Log($"{currentStep} - Complete");
 
            // Step 3: Initialize Playwright
            currentStep = "Step 3/7: Initializing Playwright";
            TestServices.Logger.Log($"{currentStep}...");
            await TestServices.Playwright.InitializeAsync().WaitAsync(cts.Token);
            TestServices.Logger.Log($"{currentStep} - Complete");
 
            // Step 4: Launch VS Code with the workspace
            currentStep = "Step 4/7: Launching VS Code";
            TestServices.Logger.Log($"{currentStep}...");
            await TestServices.VSCode.LaunchAsync(TestServices.Workspace.WorkspacePath).WaitAsync(cts.Token);
            TestServices.Logger.Log($"{currentStep} - Complete");
 
            // Step 5: Connect Playwright to VS Code
            currentStep = "Step 5/7: Connecting Playwright to VS Code via CDP";
            TestServices.Logger.Log($"{currentStep}...");
            await TestServices.Playwright.ConnectAsync(
                TestServices.Settings.RemoteDebuggingPort,
                TestServices.Workspace.Name).WaitAsync(cts.Token);
            TestServices.Logger.Log($"{currentStep} - Complete");
 
            // Step 6: Wait for VS Code UI to be ready
            currentStep = "Step 6/7: Waiting for VS Code UI";
            TestServices.Logger.Log($"{currentStep}...");
            await TestServices.VSCode.WaitForReadyAsync().WaitAsync(cts.Token);
            TestServices.Logger.Log($"{currentStep} - Complete");
 
            // Step 7: Wait for LSP and Razor to be ready
            currentStep = "Step 7/7: Waiting for LSP and Razor";
            TestServices.Logger.Log($"{currentStep}...");
            await TestServices.VSCode.WaitForLspReadyAsync().WaitAsync(cts.Token);
            await TestServices.Razor.WaitForReadyAsync().WaitAsync(cts.Token);
            TestServices.Logger.Log($"{currentStep} - Complete");
 
            TestServices.Logger.Log("Test initialization completed successfully");
        }
        catch (OperationCanceledException) when (cts.IsCancellationRequested)
        {
            var message = $"Test initialization timed out after {settings.InitializationTimeout} during: {currentStep}";
            TestServices.Logger.Log($"FATAL: {message}");
            var stepName = currentStep.Replace("/", "_").Replace(":", "").Replace(" ", "_");
            await TestServices.Playwright.TakeScreenshotAsync($"INIT_TIMEOUT_{stepName}");
            TestServices.VSCode.CollectLogsOnFailure($"INIT_TIMEOUT_{stepName}");
            throw new TimeoutException(message);
        }
        catch (Exception ex)
        {
            TestServices.Logger.Log($"FATAL: Test initialization failed during {currentStep}: {ex.Message}");
            var stepName = currentStep.Replace("/", "_").Replace(":", "").Replace(" ", "_");
            await TestServices.Playwright.TakeScreenshotAsync($"INIT_FAILED_{stepName}");
            TestServices.VSCode.CollectLogsOnFailure($"INIT_FAILED_{stepName}");
            throw;
        }
    }
 
    public virtual async Task DisposeAsync()
    {
        await TestServices.Playwright.DisposeAsync();
        TestServices.VSCode.Stop();
        TestServices.Workspace.Cleanup();
    }
}