From 8a1c45e3b7edcbece494018682df7b0ca5e52fc4 Mon Sep 17 00:00:00 2001 From: amaitland <307872+amaitland@users.noreply.github.com> Date: Sat, 21 Jun 2025 08:56:30 +1000 Subject: [PATCH] WaitForSelectorOptions and WaitForFunctionOptions add new TimeoutReturnsNull - Instead of throwing exception on timeout, the task will return null --- .../WaitTaskTests/FrameWaitForSelectorTests.cs | 9 +++++++++ lib/PuppeteerSharp/DOMWorld.cs | 5 ++++- lib/PuppeteerSharp/WaitForFunctionOptions.cs | 6 ++++++ lib/PuppeteerSharp/WaitForSelectorOptions.cs | 6 ++++++ lib/PuppeteerSharp/WaitTask.cs | 16 ++++++++++++++-- 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/lib/PuppeteerSharp.Tests/WaitTaskTests/FrameWaitForSelectorTests.cs b/lib/PuppeteerSharp.Tests/WaitTaskTests/FrameWaitForSelectorTests.cs index c473d1641..e9bf6bcac 100644 --- a/lib/PuppeteerSharp.Tests/WaitTaskTests/FrameWaitForSelectorTests.cs +++ b/lib/PuppeteerSharp.Tests/WaitTaskTests/FrameWaitForSelectorTests.cs @@ -208,6 +208,15 @@ public async Task ShouldRespectTimeout() Assert.Equal("waiting for function failed: timeout 10 ms exceeded", exception.Message); } + [PuppeteerTest("waittask.spec.ts", "Frame.waitForSelector", "should respect timeout")] + [PuppeteerFact] + public async Task ShouldRespectTimeoutReturnNull() + { + var result = await DevToolsContext.WaitForExpressionAsync("false", new WaitForFunctionOptions { Timeout = 10, TimeoutReturnsNull = true }); + + Assert.Null(result); + } + [PuppeteerTest("waittask.spec.ts", "Frame.waitForSelector", "should have an error message specifically for awaiting an element to be hidden")] [PuppeteerFact] public async Task ShouldHaveAnErrorMessageSpecificallyForAwaitingAnElementToBeHidden() diff --git a/lib/PuppeteerSharp/DOMWorld.cs b/lib/PuppeteerSharp/DOMWorld.cs index 984fa875e..179b53fd2 100644 --- a/lib/PuppeteerSharp/DOMWorld.cs +++ b/lib/PuppeteerSharp/DOMWorld.cs @@ -364,6 +364,7 @@ internal async Task WaitForFunctionAsync(string script, WaitForFunctio options.Polling, options.PollingInterval, options.Timeout ?? _timeoutSettings.Timeout, + options.TimeoutReturnsNull, args); return await waitTask @@ -380,7 +381,8 @@ internal async Task WaitForExpressionAsync(string script, WaitForFunct "function", options.Polling, options.PollingInterval, - options.Timeout ?? _timeoutSettings.Timeout); + options.Timeout ?? _timeoutSettings.Timeout, + options.TimeoutReturnsNull); return await waitTask .Task @@ -437,6 +439,7 @@ function hasVisibleBoundingBox() { polling, null, timeout, + options.TimeoutReturnsNull, new object[] { selectorOrXPath, isXPath, options.Visible, options.Hidden }); var handle = await waitTask.Task.ConfigureAwait(false); diff --git a/lib/PuppeteerSharp/WaitForFunctionOptions.cs b/lib/PuppeteerSharp/WaitForFunctionOptions.cs index cc4e2a7d0..bf5d8c50b 100644 --- a/lib/PuppeteerSharp/WaitForFunctionOptions.cs +++ b/lib/PuppeteerSharp/WaitForFunctionOptions.cs @@ -14,6 +14,12 @@ public class WaitForFunctionOptions /// public int? Timeout { get; set; } + /// + /// If set to , the method will return if the timeout is reached. + /// The default () will throw a if the timeout is reached."/> + /// + public bool TimeoutReturnsNull { get; set; } = false; + /// /// An interval at which the pageFunction is executed. defaults to /// diff --git a/lib/PuppeteerSharp/WaitForSelectorOptions.cs b/lib/PuppeteerSharp/WaitForSelectorOptions.cs index fe09c2bef..5c02c5521 100644 --- a/lib/PuppeteerSharp/WaitForSelectorOptions.cs +++ b/lib/PuppeteerSharp/WaitForSelectorOptions.cs @@ -14,6 +14,12 @@ public class WaitForSelectorOptions /// public int? Timeout { get; set; } + /// + /// If set to , the method will return if the timeout is reached. + /// The default () will throw a if the timeout is reached."/> + /// + public bool TimeoutReturnsNull { get; set; } = false; + /// /// Wait for element to be present in DOM and to be visible. /// diff --git a/lib/PuppeteerSharp/WaitTask.cs b/lib/PuppeteerSharp/WaitTask.cs index 848664fce..5462ccb48 100644 --- a/lib/PuppeteerSharp/WaitTask.cs +++ b/lib/PuppeteerSharp/WaitTask.cs @@ -14,6 +14,7 @@ internal class WaitTask : IDisposable private readonly int _timeout; private readonly object[] _args; private readonly string _title; + private readonly bool _timeoutReturnsNull; private readonly Task _timeoutTimer; private readonly CancellationTokenSource _cts; @@ -107,6 +108,7 @@ internal WaitTask( WaitForFunctionPollingOption polling, int? pollingInterval, int timeout, + bool timeoutReturnsNull, object[] args = null) { if (string.IsNullOrEmpty(predicateBody)) @@ -125,6 +127,7 @@ internal WaitTask( _timeout = timeout; _args = args ?? Array.Empty(); _title = title; + _timeoutReturnsNull = timeoutReturnsNull; _cts = new CancellationTokenSource(); @@ -136,7 +139,7 @@ internal WaitTask( { _timeoutTimer = System.Threading.Tasks.Task.Delay(timeout, _cts.Token) .ContinueWith( - _ => Terminate(new WaitTaskTimeoutException(timeout, title)), + _ => Terminate(_timeoutReturnsNull ? null : new WaitTaskTimeoutException(timeout, title)), TaskScheduler.Default); } @@ -212,7 +215,16 @@ await _world.EvaluateFunctionAsync("s => !s", success) internal void Terminate(Exception exception) { _terminated = true; - _taskCompletion.TrySetException(exception); + + if (exception == null) + { + _taskCompletion.TrySetResult(null); + } + else + { + _taskCompletion.TrySetException(exception); + } + Cleanup(); }