From f164434a020f92d56f13a3d92ce23631cdfa5d98 Mon Sep 17 00:00:00 2001 From: Felix P Date: Thu, 14 Mar 2024 12:39:44 +0100 Subject: [PATCH 1/6] Hide Create/Finish build. Implements IDisposable interface --- visual-dotnet/SauceLabs.Visual/VisualApi.cs | 15 +++--- .../SauceLabs.Visual/VisualClient.cs | 47 +++++++++++++++++-- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/visual-dotnet/SauceLabs.Visual/VisualApi.cs b/visual-dotnet/SauceLabs.Visual/VisualApi.cs index faad8aaf..896b5fcb 100644 --- a/visual-dotnet/SauceLabs.Visual/VisualApi.cs +++ b/visual-dotnet/SauceLabs.Visual/VisualApi.cs @@ -1,4 +1,5 @@ -using System.Net.Http; +using System; +using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Threading.Tasks; @@ -12,7 +13,7 @@ namespace SauceLabs.Visual { - internal class VisualApi where T : IHasCapabilities, IHasSessionId + internal class VisualApi : IDisposable where T : IHasCapabilities, IHasSessionId { private readonly string _username; private readonly string _accessKey; @@ -45,11 +46,6 @@ public VisualApi(T webdriver, Region region, string username, string accessKey, httpClient); } - ~VisualApi() - { - _graphQlClient.Dispose(); - } - public async Task>> CreateBuild(CreateBuildIn input) { var request = CreateAuthenticatedRequest(CreateBuildMutation.OperationDocument, CreateBuildMutation.OperationName, new { input }); @@ -98,5 +94,10 @@ private AuthenticatedGraphQLRequest CreateAuthenticatedRequest(string query, str Variables = variables }; } + + public void Dispose() + { + _graphQlClient.Dispose(); + } } } \ No newline at end of file diff --git a/visual-dotnet/SauceLabs.Visual/VisualClient.cs b/visual-dotnet/SauceLabs.Visual/VisualClient.cs index 5dc93a30..ee04169a 100644 --- a/visual-dotnet/SauceLabs.Visual/VisualClient.cs +++ b/visual-dotnet/SauceLabs.Visual/VisualClient.cs @@ -14,13 +14,15 @@ namespace SauceLabs.Visual /// /// VisualClient provides an access to Sauce Labs Visual services. /// - public class VisualClient + public class VisualClient : IDisposable { private readonly VisualApi _api; private readonly string _sessionId; private readonly string _jobId; private readonly string _sessionMetadataBlob; private readonly List _screenshotIds = new List(); + public VisualBuild Build { get; } + private readonly bool _externalBuild; public bool CaptureDom { get; set; } = false; /// @@ -38,6 +40,32 @@ public VisualClient(WebDriver wd, Region region, string username, string accessK var response = _api.WebDriverSessionInfo(_jobId, _sessionId).Result; var metadata = response.EnsureValidResponse(); _sessionMetadataBlob = metadata.Result.Blob; + + var createBuildResponse = CreateBuild(new CreateBuildOptions()).Result; + Build = new VisualBuild(createBuildResponse.Id, createBuildResponse.Url); + _externalBuild = false; + } + + /// + /// Creates a new instance of VisualClient + /// + /// the instance of the WebDriver session + /// the Sauce Labs region to connect to + /// the Sauce Labs username + /// the Sauce Labs access key + /// the options of the build creation + public VisualClient(WebDriver wd, Region region, string username, string accessKey, CreateBuildOptions buildOptions) + { + _api = new VisualApi(wd, region, username, accessKey); + _sessionId = wd.SessionId.ToString(); + _jobId = wd.Capabilities.HasCapability("jobUuid") ? wd.Capabilities.GetCapability("jobUuid").ToString() : _sessionId; + var response = _api.WebDriverSessionInfo(_jobId, _sessionId).Result; + var metadata = response.EnsureValidResponse(); + _sessionMetadataBlob = metadata.Result.Blob; + + var createBuildResponse = CreateBuild(buildOptions).Result; + Build = new VisualBuild(createBuildResponse.Id, createBuildResponse.Url); + _externalBuild = false; } /// @@ -45,7 +73,7 @@ public VisualClient(WebDriver wd, Region region, string username, string accessK /// /// the options for the build creation /// a VisualBuild instance - public async Task CreateBuild(CreateBuildOptions? options = null) + private async Task CreateBuild(CreateBuildOptions? options = null) { var result = (await _api.CreateBuild(new CreateBuildIn { @@ -61,7 +89,7 @@ public async Task CreateBuild(CreateBuildOptions? options = null) /// FinishBuild finishes a build /// /// the build to finish - public async Task FinishBuild(VisualBuild build) + private async Task FinishBuild(VisualBuild build) { (await _api.FinishBuild(build.Id)).EnsureValidResponse(); } @@ -73,14 +101,14 @@ public async Task FinishBuild(VisualBuild build) /// the name of the screenshot /// the configuration for the screenshot capture and comparison /// - public async Task VisualCheck(VisualBuild build, string name, VisualCheckOptions? options = null) + public async Task VisualCheck(string name, VisualCheckOptions? options = null) { var ignored = new List(); ignored.AddRange(options?.IgnoreRegions?.Select(r => new RegionIn(r)) ?? new List()); ignored.AddRange(options?.IgnoreElements?.Select(r => new RegionIn(r)) ?? new List()); var result = (await _api.CreateSnapshotFromWebDriver(new CreateSnapshotFromWebDriverIn( - buildUuid: build.Id, + buildUuid: Build.Id, name: name, jobId: _jobId, diffingMethod: options?.DiffingMethod ?? DiffingMethod.Simple, @@ -92,6 +120,15 @@ public async Task VisualCheck(VisualBuild build, string name, VisualChec return result.Result.Id; } + public void Dispose() + { + _api.Dispose(); + if (!_externalBuild) + { + FinishBuild(Build).Wait(); + } + } + /// /// VisualResults returns the results of screenshot comparison. /// From 4ae50a7e1c4bdb4717eda01d970235239fe86639 Mon Sep 17 00:00:00 2001 From: Felix P Date: Thu, 14 Mar 2024 12:42:56 +0100 Subject: [PATCH 2/6] DRY --- visual-dotnet/SauceLabs.Visual/VisualClient.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/visual-dotnet/SauceLabs.Visual/VisualClient.cs b/visual-dotnet/SauceLabs.Visual/VisualClient.cs index ee04169a..4f78a132 100644 --- a/visual-dotnet/SauceLabs.Visual/VisualClient.cs +++ b/visual-dotnet/SauceLabs.Visual/VisualClient.cs @@ -32,18 +32,8 @@ public class VisualClient : IDisposable /// the Sauce Labs region to connect to /// the Sauce Labs username /// the Sauce Labs access key - public VisualClient(WebDriver wd, Region region, string username, string accessKey) + public VisualClient(WebDriver wd, Region region, string username, string accessKey) : this(wd, region, username, accessKey, new CreateBuildOptions()) { - _api = new VisualApi(wd, region, username, accessKey); - _sessionId = wd.SessionId.ToString(); - _jobId = wd.Capabilities.HasCapability("jobUuid") ? wd.Capabilities.GetCapability("jobUuid").ToString() : _sessionId; - var response = _api.WebDriverSessionInfo(_jobId, _sessionId).Result; - var metadata = response.EnsureValidResponse(); - _sessionMetadataBlob = metadata.Result.Blob; - - var createBuildResponse = CreateBuild(new CreateBuildOptions()).Result; - Build = new VisualBuild(createBuildResponse.Id, createBuildResponse.Url); - _externalBuild = false; } /// From f82ba01cf75a1c326381412c6d83857e34040853 Mon Sep 17 00:00:00 2001 From: Felix P Date: Thu, 14 Mar 2024 12:45:33 +0100 Subject: [PATCH 3/6] Fire & Forget --- visual-dotnet/SauceLabs.Visual/VisualClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/visual-dotnet/SauceLabs.Visual/VisualClient.cs b/visual-dotnet/SauceLabs.Visual/VisualClient.cs index 4f78a132..94cb54dd 100644 --- a/visual-dotnet/SauceLabs.Visual/VisualClient.cs +++ b/visual-dotnet/SauceLabs.Visual/VisualClient.cs @@ -115,7 +115,7 @@ public void Dispose() _api.Dispose(); if (!_externalBuild) { - FinishBuild(Build).Wait(); + FinishBuild(Build); } } From 9d722f33c22a604ae341dc4a4fb7ed096f3b1d80 Mon Sep 17 00:00:00 2001 From: Felix P Date: Thu, 14 Mar 2024 12:46:54 +0100 Subject: [PATCH 4/6] Dispose after call --- visual-dotnet/SauceLabs.Visual/VisualClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/visual-dotnet/SauceLabs.Visual/VisualClient.cs b/visual-dotnet/SauceLabs.Visual/VisualClient.cs index 94cb54dd..0624597f 100644 --- a/visual-dotnet/SauceLabs.Visual/VisualClient.cs +++ b/visual-dotnet/SauceLabs.Visual/VisualClient.cs @@ -112,11 +112,11 @@ public async Task VisualCheck(string name, VisualCheckOptions? options = public void Dispose() { - _api.Dispose(); if (!_externalBuild) { FinishBuild(Build); } + _api.Dispose(); } /// From fbb3cd49ef5a2a5795a72c718171bc6e03d41232 Mon Sep 17 00:00:00 2001 From: Felix P Date: Thu, 14 Mar 2024 12:53:43 +0100 Subject: [PATCH 5/6] Introduce cleanup --- visual-dotnet/SauceLabs.Visual/VisualClient.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/visual-dotnet/SauceLabs.Visual/VisualClient.cs b/visual-dotnet/SauceLabs.Visual/VisualClient.cs index 0624597f..477a544b 100644 --- a/visual-dotnet/SauceLabs.Visual/VisualClient.cs +++ b/visual-dotnet/SauceLabs.Visual/VisualClient.cs @@ -110,12 +110,19 @@ public async Task VisualCheck(string name, VisualCheckOptions? options = return result.Result.Id; } - public void Dispose() + /// + /// Cleanup set a correct status to the build. No action should be made after that calling Cleanup. + /// + public async Task Cleanup() { if (!_externalBuild) { - FinishBuild(Build); + await FinishBuild(Build); } + } + + public void Dispose() + { _api.Dispose(); } From 04ce8dade036642580045a68810744c341a8e4da Mon Sep 17 00:00:00 2001 From: Felix P Date: Thu, 14 Mar 2024 13:56:52 +0100 Subject: [PATCH 6/6] Remove buildId from query --- visual-dotnet/SauceLabs.Visual/VisualClient.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/visual-dotnet/SauceLabs.Visual/VisualClient.cs b/visual-dotnet/SauceLabs.Visual/VisualClient.cs index 477a544b..252acc74 100644 --- a/visual-dotnet/SauceLabs.Visual/VisualClient.cs +++ b/visual-dotnet/SauceLabs.Visual/VisualClient.cs @@ -129,10 +129,9 @@ public void Dispose() /// /// VisualResults returns the results of screenshot comparison. /// - /// the id of the build to check. /// a dictionary containing DiffStatus and the number of screenshot in that status. /// - public async Task> VisualResults(string buildId) + public async Task> VisualResults() { var policyOptions = new RetryPolicyOptions { @@ -140,7 +139,7 @@ public async Task> VisualResults(string buildId) MaxRetryInterval = TimeSpan.FromSeconds(5), MaxRetrySteps = 10 }; - var result = await Retry.Do(async () => await FetchVisualResults(buildId), + var result = await Retry.Do(async () => await FetchVisualResults(Build.Id), retryInterval: TimeSpan.FromMilliseconds(100), retryLimit: 10, retryPolicy: RetryPolicy.ExponentialBackoff,