diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index f18d662..d30b7ac 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -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:
@@ -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 }}
diff --git a/LambdaTest.Playwright.Driver/LambdaTest.Playwright.Driver.csproj b/LambdaTest.Playwright.Driver/LambdaTest.Playwright.Driver.csproj
new file mode 100644
index 0000000..0c98589
--- /dev/null
+++ b/LambdaTest.Playwright.Driver/LambdaTest.Playwright.Driver.csproj
@@ -0,0 +1,18 @@
+
+
+ net8.0
+ LambdaTest.Playwright.Driver
+ 1.0.0
+ Lambdatest-SmartUI
+ LambdaTest
+ LambdaTest C# Playwright SDK with SmartUI support
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/LambdaTest.Playwright.Driver/SmartUI.cs b/LambdaTest.Playwright.Driver/SmartUI.cs
new file mode 100644
index 0000000..5185b30
--- /dev/null
+++ b/LambdaTest.Playwright.Driver/SmartUI.cs
@@ -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? 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(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();
+ }
+
+ // Get test details from LambdaTestHook to extract the test ID
+ string sessionId = "";
+ try
+ {
+ var testDetailsResponse = await page.EvaluateAsync("_ => {}", "lambdatest_action: {\"action\": \"getTestDetails\"}");
+ if (!string.IsNullOrEmpty(testDetailsResponse))
+ {
+ var testDetails = JsonSerializer.Deserialize(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(snapshotScript);
+ if (domJSON == null)
+ {
+ throw new Exception("Failed to capture DOM object.");
+ }
+
+ var domContent = JsonSerializer.Deserialize(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(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? 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? 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 Warnings { get; set; } = new List();
+ }
+
+ 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 warnings { get; set; } = new List();
+ public List resources { get; set; } = new List();
+ public List hints { get; set; } = new List();
+ }
+
+ 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;
+ }
+ }
+}
diff --git a/LambdaTest.Sdk.Utils/Constants.cs b/LambdaTest.Sdk.Utils/Constants.cs
index 3d363c5..c84eaf3 100644
--- a/LambdaTest.Sdk.Utils/Constants.cs
+++ b/LambdaTest.Sdk.Utils/Constants.cs
@@ -9,7 +9,7 @@ public static string GetSmartUIServerAddress()
var address = Environment.GetEnvironmentVariable("SMARTUI_SERVER_ADDRESS");
if (string.IsNullOrEmpty(address))
{
- throw new Exception("SmartUI server address not found");
+ return "http://localhost:49152";
}
return address;
}
diff --git a/LambdaTest.Sdk.Utils/LambdaTest.Sdk.Utils.csproj b/LambdaTest.Sdk.Utils/LambdaTest.Sdk.Utils.csproj
index 6eca1e8..f2fe13e 100644
--- a/LambdaTest.Sdk.Utils/LambdaTest.Sdk.Utils.csproj
+++ b/LambdaTest.Sdk.Utils/LambdaTest.Sdk.Utils.csproj
@@ -2,7 +2,7 @@
net8.0
LambdaTest.Sdk.Utils
- 1.0.2
+ 1.0.3
Lambdatest-SmartUI
LambdaTest
LambdaTest SDK Utilities for SmartUI CLI in C#
diff --git a/LambdaTest.Selenium.Driver/LambdaTest.Selenium.Driver.csproj b/LambdaTest.Selenium.Driver/LambdaTest.Selenium.Driver.csproj
index e23a09c..1990d42 100644
--- a/LambdaTest.Selenium.Driver/LambdaTest.Selenium.Driver.csproj
+++ b/LambdaTest.Selenium.Driver/LambdaTest.Selenium.Driver.csproj
@@ -2,7 +2,7 @@
net8.0
LambdaTest.Selenium.Driver
- 1.0.2
+ 1.0.3
Lambdatest-SmartUI
LambdaTest
LambdaTest C# Selenium SDK
diff --git a/LambdaTest.Selenium.Driver/SmartUI.cs b/LambdaTest.Selenium.Driver/SmartUI.cs
index 02023f4..2469d50 100644
--- a/LambdaTest.Selenium.Driver/SmartUI.cs
+++ b/LambdaTest.Selenium.Driver/SmartUI.cs
@@ -4,13 +4,14 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using OpenQA.Selenium;
+using OpenQA.Selenium.Remote;
using LambdaTest.Sdk.Utils;
namespace LambdaTest.Selenium.Driver
{
public static class SmartUISnapshot
{
- private static readonly ILogger SmartUILogger = Logger.CreateLogger("Lambdatest.Selenium.Driver");
+ private static readonly ILogger SmartUILogger = Logger.CreateLogger("Lambdatest.Selenium.Driver");
public static async Task CaptureSnapshot(IWebDriver driver, string name, Dictionary? options = null)
{
@@ -44,6 +45,23 @@ public static async Task CaptureSnapshot(IWebDriver driver, string name, Diction
((IJavaScriptExecutor)driver).ExecuteScript(script);
+ // Extract sessionId from driver
+ string sessionId = null;
+ if (driver is RemoteWebDriver remoteDriver)
+ {
+ sessionId = remoteDriver.SessionId.ToString();
+ }
+
+ if (!string.IsNullOrEmpty(sessionId))
+ {
+ // Append sessionId to options
+ if (options == null)
+ {
+ options = new Dictionary();
+ }
+ options["sessionId"] = sessionId;
+ }
+
var optionsJSON = JsonSerializer.Serialize(options);
var snapshotScript = @"
var options = " + optionsJSON + @";
@@ -129,7 +147,6 @@ private class DomDeserializerResponse
{
public DomJSONContent Dom { get; set; } = new DomJSONContent();
public string Url { get; set; } = string.Empty;
-
}
}
-}
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index cf0948a..b6a1e09 100644
--- a/README.md
+++ b/README.md
@@ -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
@@ -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
-dotnet add package LambdaTest.Sdk.Utils --version 1.0.2
-dotnet add package LambdaTest.Selenium.Driver --version 1.0.2
+# 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
diff --git a/Tests/Program.cs b/Tests/Program.cs
new file mode 100644
index 0000000..36db9b9
--- /dev/null
+++ b/Tests/Program.cs
@@ -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}");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj
new file mode 100644
index 0000000..afa8ef6
--- /dev/null
+++ b/Tests/Tests.csproj
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+
+
+