Skip to content
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
11 changes: 10 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ on:
workflow_dispatch:
inputs:
package-name:
description: 'Package to Publish (utils/selenium)'
description: 'Package to Publish (utils/selenium/playwright)'
required: true
type: choice
options:
- lambdatest-sdk-utils
- lambdatest-selenium-driver
- lambdatest-playwright-driver
default: 'lambdatest-sdk-utils'

jobs:
Expand Down Expand Up @@ -40,3 +41,11 @@ jobs:
dotnet build ./LambdaTest.Selenium.Driver --configuration Release --no-restore
dotnet pack ./LambdaTest.Selenium.Driver --configuration Release --no-build --output ./LambdaTest.Selenium.Driver/nupkgs
dotnet nuget push ./LambdaTest.Selenium.Driver/nupkgs/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}

- name: Build and publish LambdaTest.Playwright.Driver
if: github.event.inputs.package-name == 'lambdatest-playwright-driver'
run: |
dotnet restore ./LambdaTest.Playwright.Driver
dotnet build ./LambdaTest.Playwright.Driver --configuration Release --no-restore
dotnet pack ./LambdaTest.Playwright.Driver --configuration Release --no-build --output ./LambdaTest.Playwright.Driver/nupkgs
dotnet nuget push ./LambdaTest.Playwright.Driver/nupkgs/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
18 changes: 18 additions & 0 deletions LambdaTest.Playwright.Driver/LambdaTest.Playwright.Driver.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<PackageId>LambdaTest.Playwright.Driver</PackageId>
<Version>1.0.0</Version>
<Authors>Lambdatest-SmartUI</Authors>
<Company>LambdaTest</Company>
<Description>LambdaTest C# Playwright SDK with SmartUI support</Description>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LambdaTest.Sdk.Utils" Version="1.0.3" />
<PackageReference Include="Microsoft.Playwright" Version="1.47.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
</ItemGroup>
</Project>
203 changes: 203 additions & 0 deletions LambdaTest.Playwright.Driver/SmartUI.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Playwright;
using LambdaTest.Sdk.Utils;

namespace LambdaTest.Playwright.Driver
{
public static class SmartUISnapshot
{
private static readonly ILogger SmartUILogger = Logger.CreateLogger("Lambdatest.Playwright.Driver");
public static async Task CaptureSnapshot(IPage page, string name, Dictionary<string, object>? options = null)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("The `snapshotName` argument is required.", nameof(name));
}

if (!await LambdaTest.Sdk.Utils.SmartUI.IsSmartUIEnabled())
{
throw new Exception("Cannot find SmartUI server.");
}

try
{
var domSerializerResponse = await LambdaTest.Sdk.Utils.SmartUI.FetchDomSerializer();
if (domSerializerResponse == null)
{
throw new Exception("Failed to fetch DOM serializer script response.");
}
var domSerializerScript = JsonSerializer.Deserialize<FetchDomSerializerResponse>(domSerializerResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (domSerializerScript?.Data?.Dom == null)
{
throw new Exception("Failed to json serialize the DOM serializer script.");
}

string script = domSerializerScript.Data.Dom;

if (options == null)
{
options = new Dictionary<string, object>();
}

// Get test details from LambdaTestHook to extract the test ID
string sessionId = "";
try
{
var testDetailsResponse = await page.EvaluateAsync<string>("_ => {}", "lambdatest_action: {\"action\": \"getTestDetails\"}");
if (!string.IsNullOrEmpty(testDetailsResponse))
{
var testDetails = JsonSerializer.Deserialize<TestDetailsResponse>(testDetailsResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
sessionId = testDetails?.Data?.SessionId ?? $"playwright_{Guid.NewGuid():N}";
}
}
catch (Exception)
{
SmartUILogger.LogError("Failed to get test details from LambdaTestHook.");
}
if (!string.IsNullOrEmpty(sessionId))
{
// Append sessionId to options
options["sessionId"] = sessionId;
}
// Execute the DOM serializer script in the page context
await page.EvaluateAsync(script);
var optionsJSON = JsonSerializer.Serialize(options);
var snapshotScript = @"
() => {
var options = " + optionsJSON + @";
return JSON.stringify({
dom: SmartUIDOM.serialize(options),
url: document.URL
});
}";

var domJSON = await page.EvaluateAsync<string>(snapshotScript);
if (domJSON == null)
{
throw new Exception("Failed to capture DOM object.");
}

var domContent = JsonSerializer.Deserialize<DomDeserializerResponse>(domJSON, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

if (domContent == null)
{
throw new Exception("Failed to convert DOM object into JSON");
}

var dom = new LambdaTest.Sdk.Utils.SmartUI.DomObject
{
Dom = new LambdaTest.Sdk.Utils.SmartUI.DomContent
{
html = domContent.Dom.html,
warnings = domContent.Dom.warnings,
resources = domContent.Dom.resources,
hints = domContent.Dom.hints
},
Name = name,
Url = domContent.Url
};

var apiResponseJSON = await LambdaTest.Sdk.Utils.SmartUI.PostSnapshot(dom, "lambdatest-csharp-playwright-driver", options);
var apiResponse = JsonSerializer.Deserialize<ApiResponse>(apiResponseJSON, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

if (apiResponse?.Data?.Warnings != null && apiResponse.Data.Warnings.Count > 0)
{
foreach (var warning in apiResponse.Data.Warnings)
{
SmartUILogger.LogWarning(warning);
}
}

SmartUILogger.LogInformation($"Snapshot captured: {name}");
}
catch (Exception e)
{
SmartUILogger.LogError($"Playwright snapshot failed: {name}");
SmartUILogger.LogError(e.ToString());
throw;
}
}

public static async Task CaptureSnapshot(IBrowserContext context, string name, Dictionary<string, object>? options = null)
{
// Capture snapshot from the first page in the context
var pages = context.Pages;
if (pages.Count > 0)
{
await CaptureSnapshot(pages[0], name, options);
}
else
{
throw new Exception("No pages available in the browser context for snapshot capture.");
}
}

public static async Task CaptureSnapshot(IBrowser browser, string name, Dictionary<string, object>? options = null)
{
// Capture snapshot from the first context in the browser
var contexts = browser.Contexts;
if (contexts.Count > 0)
{
await CaptureSnapshot(contexts[0], name, options);
}
else
{
throw new Exception("No browser contexts available for snapshot capture.");
}
}

private class ApiResponse
{
public ApiData Data { get; set; } = new ApiData();
}

private class ApiData
{
public string Message { get; set; } = string.Empty;
public List<string> Warnings { get; set; } = new List<string>();
}

private class FetchDomSerializerResponse
{
public FetchDomSerializerData Data { get; set; } = new FetchDomSerializerData();
}

private class FetchDomSerializerData
{
public string Dom { get; set; } = string.Empty;
}

private class DomJSONContent
{
public string html { get; set; } = string.Empty;
public List<string> warnings { get; set; } = new List<string>();
public List<LambdaTest.Sdk.Utils.SmartUI.Resource> resources { get; set; } = new List<LambdaTest.Sdk.Utils.SmartUI.Resource>();
public List<string> hints { get; set; } = new List<string>();
}

private class DomDeserializerResponse
{
public DomJSONContent Dom { get; set; } = new DomJSONContent();
public string Url { get; set; } = string.Empty;
}

private class TestDetailsResponse
{
[System.Text.Json.Serialization.JsonPropertyName("data")]
public TestDetailsData Data { get; set; } = new TestDetailsData();
}

private class TestDetailsData
{
[System.Text.Json.Serialization.JsonPropertyName("test_id")]
public string TestId { get; set; } = string.Empty;

[System.Text.Json.Serialization.JsonPropertyName("session_id")]
public string SessionId { get; set; } = string.Empty;
}
}
}
45 changes: 44 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Overview

The LambdaTest C# SDK provides a set of utilities and integrations for working with LambdaTest's SmartUI and Selenium services. This SDK is designed to streamline the process of capturing and analyzing SmartUI snapshots using various testing frameworks.
The LambdaTest C# SDK provides a set of utilities and integrations for working with LambdaTest's SmartUI and Selenium & Playwright services. This SDK is designed to streamline the process of capturing and analyzing SmartUI snapshots using various testing frameworks.

## Packages

Expand All @@ -21,14 +21,57 @@ LambdaTest.Selenium.Driver integrates seamlessly with Selenium WebDriver to capt

- SmartUI snapshot capture using Selenium WebDriver
- Integration with LambdaTest SmartUI CLI
- Support for RemoteWebDriver and local WebDriver instances

### LambdaTest.Playwright.Driver

LambdaTest.Playwright.Driver provides Playwright integration with LambdaTest SmartUI for capturing visual snapshots during automated testing. Features include:

- SmartUI snapshot capture using Playwright
- Multi-level support (Page, BrowserContext, Browser)
- Cross-browser support (Chromium, Firefox, WebKit)

## Installation

To install the packages, use the .NET CLI:

```sh
# Core utilities
dotnet add package LambdaTest.Sdk.Utils --version 1.0.3

# Selenium integration
dotnet add package LambdaTest.Selenium.Driver --version 1.0.3

# Playwright integration
dotnet add package LambdaTest.Playwright.Driver --version 1.0.0
```

## Quick Start

### Selenium Example

```csharp
using OpenQA.Selenium;
using LambdaTest.Selenium.Driver;

var driver = new ChromeDriver();
driver.Navigate().GoToUrl("https://example.com");

await SmartUISnapshot.CaptureSnapshot(driver, "my-snapshot");
```

### Playwright Example

```csharp
using Microsoft.Playwright;
using LambdaTest.Playwright.Driver;

using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync();
var page = await browser.NewPageAsync();

await page.GotoAsync("https://example.com");
await SmartUISnapshot.CaptureSnapshot(page, "my-snapshot");
```

## License
Expand Down
78 changes: 78 additions & 0 deletions Tests/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Playwright;
using LambdaTest.Playwright.Driver;

namespace Tests
{
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("LambdaTest Playwright Driver Test");
Console.WriteLine("===============================");

try
{
// Initialize Playwright
using var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = true
});

var context = await browser.NewContextAsync();
var page = await context.NewPageAsync();

// Navigate to a test page
await page.GotoAsync("https://example.com");
Console.WriteLine("✓ Successfully navigated to test page");

// Test basic snapshot capture
try
{
await SmartUISnapshot.CaptureSnapshot(page, "test-snapshot");
Console.WriteLine("✓ Basic snapshot capture successful");
}
catch (Exception ex)
{
Console.WriteLine($"x Snapshot capture failed (expected if SmartUI server not running): {ex.Message}");
}

// Test context-level snapshot
try
{
await SmartUISnapshot.CaptureSnapshot(context, "context-snapshot");
Console.WriteLine("✓ Context-level snapshot capture successful");
}
catch (Exception ex)
{
Console.WriteLine($"x Context-level snapshot capture failed: {ex.Message}");
}

// Test browser-level snapshot
try
{
await SmartUISnapshot.CaptureSnapshot(browser, "browser-snapshot");
Console.WriteLine("✓ Browser-level snapshot capture successful");
}
catch (Exception ex)
{
Console.WriteLine($"x Browser-level snapshot capture failed: {ex.Message}");
}

// Cleanup
await page.CloseAsync();
await context.CloseAsync();
await browser.CloseAsync();

Console.WriteLine("\n✓ All operations completed!");
}
catch (Exception ex)
{
Console.WriteLine($"Operation failed: {ex.Message}");
}
}
}
}
Loading