Skip to content

Commit 1e423f6

Browse files
authored
Merge pull request #8 from LambdaTest/stage
Release lambdatest-playwright-driver
2 parents b107a88 + 33c3150 commit 1e423f6

File tree

6 files changed

+369
-2
lines changed

6 files changed

+369
-2
lines changed

.github/workflows/publish.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ on:
44
workflow_dispatch:
55
inputs:
66
package-name:
7-
description: 'Package to Publish (utils/selenium)'
7+
description: 'Package to Publish (utils/selenium/playwright)'
88
required: true
99
type: choice
1010
options:
1111
- lambdatest-sdk-utils
1212
- lambdatest-selenium-driver
13+
- lambdatest-playwright-driver
1314
default: 'lambdatest-sdk-utils'
1415

1516
jobs:
@@ -40,3 +41,11 @@ jobs:
4041
dotnet build ./LambdaTest.Selenium.Driver --configuration Release --no-restore
4142
dotnet pack ./LambdaTest.Selenium.Driver --configuration Release --no-build --output ./LambdaTest.Selenium.Driver/nupkgs
4243
dotnet nuget push ./LambdaTest.Selenium.Driver/nupkgs/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
44+
45+
- name: Build and publish LambdaTest.Playwright.Driver
46+
if: github.event.inputs.package-name == 'lambdatest-playwright-driver'
47+
run: |
48+
dotnet restore ./LambdaTest.Playwright.Driver
49+
dotnet build ./LambdaTest.Playwright.Driver --configuration Release --no-restore
50+
dotnet pack ./LambdaTest.Playwright.Driver --configuration Release --no-build --output ./LambdaTest.Playwright.Driver/nupkgs
51+
dotnet nuget push ./LambdaTest.Playwright.Driver/nupkgs/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net8.0</TargetFramework>
4+
<PackageId>LambdaTest.Playwright.Driver</PackageId>
5+
<Version>1.0.0</Version>
6+
<Authors>Lambdatest-SmartUI</Authors>
7+
<Company>LambdaTest</Company>
8+
<Description>LambdaTest C# Playwright SDK with SmartUI support</Description>
9+
<ImplicitUsings>enable</ImplicitUsings>
10+
<Nullable>enable</Nullable>
11+
</PropertyGroup>
12+
<ItemGroup>
13+
<PackageReference Include="LambdaTest.Sdk.Utils" Version="1.0.3" />
14+
<PackageReference Include="Microsoft.Playwright" Version="1.47.0" />
15+
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
16+
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
17+
</ItemGroup>
18+
</Project>
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text.Json;
4+
using System.Threading.Tasks;
5+
using Microsoft.Extensions.Logging;
6+
using Microsoft.Playwright;
7+
using LambdaTest.Sdk.Utils;
8+
9+
namespace LambdaTest.Playwright.Driver
10+
{
11+
public static class SmartUISnapshot
12+
{
13+
private static readonly ILogger SmartUILogger = Logger.CreateLogger("Lambdatest.Playwright.Driver");
14+
public static async Task CaptureSnapshot(IPage page, string name, Dictionary<string, object>? options = null)
15+
{
16+
if (string.IsNullOrEmpty(name))
17+
{
18+
throw new ArgumentException("The `snapshotName` argument is required.", nameof(name));
19+
}
20+
21+
if (!await LambdaTest.Sdk.Utils.SmartUI.IsSmartUIEnabled())
22+
{
23+
throw new Exception("Cannot find SmartUI server.");
24+
}
25+
26+
try
27+
{
28+
var domSerializerResponse = await LambdaTest.Sdk.Utils.SmartUI.FetchDomSerializer();
29+
if (domSerializerResponse == null)
30+
{
31+
throw new Exception("Failed to fetch DOM serializer script response.");
32+
}
33+
var domSerializerScript = JsonSerializer.Deserialize<FetchDomSerializerResponse>(domSerializerResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
34+
if (domSerializerScript?.Data?.Dom == null)
35+
{
36+
throw new Exception("Failed to json serialize the DOM serializer script.");
37+
}
38+
39+
string script = domSerializerScript.Data.Dom;
40+
41+
if (options == null)
42+
{
43+
options = new Dictionary<string, object>();
44+
}
45+
46+
// Get test details from LambdaTestHook to extract the test ID
47+
string sessionId = "";
48+
try
49+
{
50+
var testDetailsResponse = await page.EvaluateAsync<string>("_ => {}", "lambdatest_action: {\"action\": \"getTestDetails\"}");
51+
if (!string.IsNullOrEmpty(testDetailsResponse))
52+
{
53+
var testDetails = JsonSerializer.Deserialize<TestDetailsResponse>(testDetailsResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
54+
sessionId = testDetails?.Data?.SessionId ?? $"playwright_{Guid.NewGuid():N}";
55+
}
56+
}
57+
catch (Exception)
58+
{
59+
SmartUILogger.LogError("Failed to get test details from LambdaTestHook.");
60+
}
61+
if (!string.IsNullOrEmpty(sessionId))
62+
{
63+
// Append sessionId to options
64+
options["sessionId"] = sessionId;
65+
}
66+
// Execute the DOM serializer script in the page context
67+
await page.EvaluateAsync(script);
68+
var optionsJSON = JsonSerializer.Serialize(options);
69+
var snapshotScript = @"
70+
() => {
71+
var options = " + optionsJSON + @";
72+
return JSON.stringify({
73+
dom: SmartUIDOM.serialize(options),
74+
url: document.URL
75+
});
76+
}";
77+
78+
var domJSON = await page.EvaluateAsync<string>(snapshotScript);
79+
if (domJSON == null)
80+
{
81+
throw new Exception("Failed to capture DOM object.");
82+
}
83+
84+
var domContent = JsonSerializer.Deserialize<DomDeserializerResponse>(domJSON, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
85+
86+
if (domContent == null)
87+
{
88+
throw new Exception("Failed to convert DOM object into JSON");
89+
}
90+
91+
var dom = new LambdaTest.Sdk.Utils.SmartUI.DomObject
92+
{
93+
Dom = new LambdaTest.Sdk.Utils.SmartUI.DomContent
94+
{
95+
html = domContent.Dom.html,
96+
warnings = domContent.Dom.warnings,
97+
resources = domContent.Dom.resources,
98+
hints = domContent.Dom.hints
99+
},
100+
Name = name,
101+
Url = domContent.Url
102+
};
103+
104+
var apiResponseJSON = await LambdaTest.Sdk.Utils.SmartUI.PostSnapshot(dom, "lambdatest-csharp-playwright-driver", options);
105+
var apiResponse = JsonSerializer.Deserialize<ApiResponse>(apiResponseJSON, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
106+
107+
if (apiResponse?.Data?.Warnings != null && apiResponse.Data.Warnings.Count > 0)
108+
{
109+
foreach (var warning in apiResponse.Data.Warnings)
110+
{
111+
SmartUILogger.LogWarning(warning);
112+
}
113+
}
114+
115+
SmartUILogger.LogInformation($"Snapshot captured: {name}");
116+
}
117+
catch (Exception e)
118+
{
119+
SmartUILogger.LogError($"Playwright snapshot failed: {name}");
120+
SmartUILogger.LogError(e.ToString());
121+
throw;
122+
}
123+
}
124+
125+
public static async Task CaptureSnapshot(IBrowserContext context, string name, Dictionary<string, object>? options = null)
126+
{
127+
// Capture snapshot from the first page in the context
128+
var pages = context.Pages;
129+
if (pages.Count > 0)
130+
{
131+
await CaptureSnapshot(pages[0], name, options);
132+
}
133+
else
134+
{
135+
throw new Exception("No pages available in the browser context for snapshot capture.");
136+
}
137+
}
138+
139+
public static async Task CaptureSnapshot(IBrowser browser, string name, Dictionary<string, object>? options = null)
140+
{
141+
// Capture snapshot from the first context in the browser
142+
var contexts = browser.Contexts;
143+
if (contexts.Count > 0)
144+
{
145+
await CaptureSnapshot(contexts[0], name, options);
146+
}
147+
else
148+
{
149+
throw new Exception("No browser contexts available for snapshot capture.");
150+
}
151+
}
152+
153+
private class ApiResponse
154+
{
155+
public ApiData Data { get; set; } = new ApiData();
156+
}
157+
158+
private class ApiData
159+
{
160+
public string Message { get; set; } = string.Empty;
161+
public List<string> Warnings { get; set; } = new List<string>();
162+
}
163+
164+
private class FetchDomSerializerResponse
165+
{
166+
public FetchDomSerializerData Data { get; set; } = new FetchDomSerializerData();
167+
}
168+
169+
private class FetchDomSerializerData
170+
{
171+
public string Dom { get; set; } = string.Empty;
172+
}
173+
174+
private class DomJSONContent
175+
{
176+
public string html { get; set; } = string.Empty;
177+
public List<string> warnings { get; set; } = new List<string>();
178+
public List<LambdaTest.Sdk.Utils.SmartUI.Resource> resources { get; set; } = new List<LambdaTest.Sdk.Utils.SmartUI.Resource>();
179+
public List<string> hints { get; set; } = new List<string>();
180+
}
181+
182+
private class DomDeserializerResponse
183+
{
184+
public DomJSONContent Dom { get; set; } = new DomJSONContent();
185+
public string Url { get; set; } = string.Empty;
186+
}
187+
188+
private class TestDetailsResponse
189+
{
190+
[System.Text.Json.Serialization.JsonPropertyName("data")]
191+
public TestDetailsData Data { get; set; } = new TestDetailsData();
192+
}
193+
194+
private class TestDetailsData
195+
{
196+
[System.Text.Json.Serialization.JsonPropertyName("test_id")]
197+
public string TestId { get; set; } = string.Empty;
198+
199+
[System.Text.Json.Serialization.JsonPropertyName("session_id")]
200+
public string SessionId { get; set; } = string.Empty;
201+
}
202+
}
203+
}

README.md

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Overview
44

5-
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.
5+
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.
66

77
## Packages
88

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

2222
- SmartUI snapshot capture using Selenium WebDriver
2323
- Integration with LambdaTest SmartUI CLI
24+
- Support for RemoteWebDriver and local WebDriver instances
25+
26+
### LambdaTest.Playwright.Driver
27+
28+
LambdaTest.Playwright.Driver provides Playwright integration with LambdaTest SmartUI for capturing visual snapshots during automated testing. Features include:
29+
30+
- SmartUI snapshot capture using Playwright
31+
- Multi-level support (Page, BrowserContext, Browser)
32+
- Cross-browser support (Chromium, Firefox, WebKit)
2433

2534
## Installation
2635

2736
To install the packages, use the .NET CLI:
2837

2938
```sh
39+
# Core utilities
3040
dotnet add package LambdaTest.Sdk.Utils --version 1.0.3
41+
42+
# Selenium integration
3143
dotnet add package LambdaTest.Selenium.Driver --version 1.0.3
44+
45+
# Playwright integration
46+
dotnet add package LambdaTest.Playwright.Driver --version 1.0.0
47+
```
48+
49+
## Quick Start
50+
51+
### Selenium Example
52+
53+
```csharp
54+
using OpenQA.Selenium;
55+
using LambdaTest.Selenium.Driver;
56+
57+
var driver = new ChromeDriver();
58+
driver.Navigate().GoToUrl("https://example.com");
59+
60+
await SmartUISnapshot.CaptureSnapshot(driver, "my-snapshot");
61+
```
62+
63+
### Playwright Example
64+
65+
```csharp
66+
using Microsoft.Playwright;
67+
using LambdaTest.Playwright.Driver;
68+
69+
using var playwright = await Playwright.CreateAsync();
70+
await using var browser = await playwright.Chromium.LaunchAsync();
71+
var page = await browser.NewPageAsync();
72+
73+
await page.GotoAsync("https://example.com");
74+
await SmartUISnapshot.CaptureSnapshot(page, "my-snapshot");
3275
```
3376

3477
## License

Tests/Program.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using Microsoft.Playwright;
5+
using LambdaTest.Playwright.Driver;
6+
7+
namespace Tests
8+
{
9+
class Program
10+
{
11+
static async Task Main(string[] args)
12+
{
13+
Console.WriteLine("LambdaTest Playwright Driver Test");
14+
Console.WriteLine("===============================");
15+
16+
try
17+
{
18+
// Initialize Playwright
19+
using var playwright = await Playwright.CreateAsync();
20+
var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
21+
{
22+
Headless = true
23+
});
24+
25+
var context = await browser.NewContextAsync();
26+
var page = await context.NewPageAsync();
27+
28+
// Navigate to a test page
29+
await page.GotoAsync("https://example.com");
30+
Console.WriteLine("✓ Successfully navigated to test page");
31+
32+
// Test basic snapshot capture
33+
try
34+
{
35+
await SmartUISnapshot.CaptureSnapshot(page, "test-snapshot");
36+
Console.WriteLine("✓ Basic snapshot capture successful");
37+
}
38+
catch (Exception ex)
39+
{
40+
Console.WriteLine($"x Snapshot capture failed (expected if SmartUI server not running): {ex.Message}");
41+
}
42+
43+
// Test context-level snapshot
44+
try
45+
{
46+
await SmartUISnapshot.CaptureSnapshot(context, "context-snapshot");
47+
Console.WriteLine("✓ Context-level snapshot capture successful");
48+
}
49+
catch (Exception ex)
50+
{
51+
Console.WriteLine($"x Context-level snapshot capture failed: {ex.Message}");
52+
}
53+
54+
// Test browser-level snapshot
55+
try
56+
{
57+
await SmartUISnapshot.CaptureSnapshot(browser, "browser-snapshot");
58+
Console.WriteLine("✓ Browser-level snapshot capture successful");
59+
}
60+
catch (Exception ex)
61+
{
62+
Console.WriteLine($"x Browser-level snapshot capture failed: {ex.Message}");
63+
}
64+
65+
// Cleanup
66+
await page.CloseAsync();
67+
await context.CloseAsync();
68+
await browser.CloseAsync();
69+
70+
Console.WriteLine("\n✓ All operations completed!");
71+
}
72+
catch (Exception ex)
73+
{
74+
Console.WriteLine($"Operation failed: {ex.Message}");
75+
}
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)