Skip to content

Commit 64a2b19

Browse files
authored
Improve errors from frame.waitFor* methods (#420)
1 parent 1497c79 commit 64a2b19

File tree

9 files changed

+122
-24
lines changed

9 files changed

+122
-24
lines changed

lib/PuppeteerSharp.Tests/FrameTests/WaitForFunctionTests.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ public async Task ShouldAcceptElementHandleArguments()
8383
await waitForFunction;
8484
}
8585

86+
[Fact]
87+
public async Task ShouldRespectTimeout()
88+
{
89+
var exception = await Assert.ThrowsAsync<WaitTaskTimeoutException>(()
90+
=> Page.WaitForExpressionAsync("false", new WaitForFunctionOptions { Timeout = 10 }));
91+
92+
Assert.Contains("waiting for function failed: timeout", exception.Message);
93+
}
94+
8695
[Fact]
8796
public async Task ShouldDisableTimeoutWhenItsSetTo0()
8897
{

lib/PuppeteerSharp.Tests/FrameTests/WaitForSelectorTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,11 @@ public async Task HiddenShouldWaitForRemoval()
175175
[Fact]
176176
public async Task ShouldRespectTimeout()
177177
{
178-
var exception = await Assert.ThrowsAnyAsync<PuppeteerException>(async ()
178+
var exception = await Assert.ThrowsAsync<WaitTaskTimeoutException>(async ()
179179
=> await Page.WaitForSelectorAsync("div", new WaitForSelectorOptions { Timeout = 10 }));
180180

181181
Assert.NotNull(exception);
182-
Assert.Contains("waiting failed: timeout", exception.Message);
182+
Assert.Contains("waiting for selector 'div' failed: timeout", exception.Message);
183183
}
184184

185185
[Fact]

lib/PuppeteerSharp.Tests/FrameTests/WaitForXPathTests.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,14 @@ public async Task ShouldAllowYouToSelectAnElementWithSingleSlash()
9696
var waitForXPath = Page.WaitForXPathAsync("/html/body/div");
9797
Assert.Equal("some text", await Page.EvaluateFunctionAsync<string>("x => x.textContent", await waitForXPath));
9898
}
99+
100+
[Fact]
101+
public async Task ShouldRespectTimeout()
102+
{
103+
var exception = await Assert.ThrowsAsync<WaitTaskTimeoutException>(()
104+
=> Page.WaitForXPathAsync("//div", new WaitForSelectorOptions { Timeout = 10 }));
105+
106+
Assert.Contains("waiting for XPath '//div' failed: timeout", exception.Message);
107+
}
99108
}
100109
}

lib/PuppeteerSharp/Frame.cs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -262,21 +262,32 @@ public Task<ElementHandle> WaitForXPathAsync(string xpath, WaitForSelectorOption
262262
public Task WaitForTimeoutAsync(int milliseconds) => Task.Delay(milliseconds);
263263

264264
/// <summary>
265-
/// Waits for a script to be evaluated to a truthy value
265+
/// Waits for a function to be evaluated to a truthy value
266266
/// </summary>
267267
/// <param name="script">Function to be evaluated in browser context</param>
268268
/// <param name="options">Optional waiting parameters</param>
269269
/// <param name="args">Arguments to pass to <c>script</c></param>
270270
/// <returns>A task that resolves when the <c>script</c> returns a truthy value</returns>
271271
/// <seealso cref="Page.WaitForFunctionAsync(string, WaitForFunctionOptions, object[])"/>
272272
public Task<JSHandle> WaitForFunctionAsync(string script, WaitForFunctionOptions options, params object[] args)
273-
=> new WaitTask(this, script, options.Polling, options.PollingInterval, options.Timeout, args).Task;
273+
=> new WaitTask(this, script, false, "function", options.Polling, options.PollingInterval, options.Timeout, args).Task;
274+
275+
/// <summary>
276+
/// Waits for an expression to be evaluated to a truthy value
277+
/// </summary>
278+
/// <param name="script">Expression to be evaluated in browser context</param>
279+
/// <param name="options">Optional waiting parameters</param>
280+
/// <param name="args">Arguments to pass to <c>script</c></param>
281+
/// <returns>A task that resolves when the <c>script</c> returns a truthy value</returns>
282+
/// <seealso cref="Page.WaitForExpressionAsync(string, WaitForFunctionOptions)"/>
283+
public Task<JSHandle> WaitForExpressionAsync(string script, WaitForFunctionOptions options)
284+
=> new WaitTask(this, script, true, "function", options.Polling, options.PollingInterval, options.Timeout).Task;
274285

275286
/// <summary>
276287
/// Triggers a change and input event once all the provided options have been selected.
277288
/// If there's no <![CDATA[<select>]]> element matching selector, the method throws an error.
278289
/// </summary>
279-
/// <exception cref="SelectorException">If there's no element matching <paramref name="selector"/></exception>
290+
/// <exception cref="SelectorException">If there's no element matching <paramref name="selector"/></exception>
280291
/// <param name="selector">A selector to query page for</param>
281292
/// <param name="values">Values of options to select. If the <![CDATA[<select>]]> has the multiple attribute,
282293
/// all values are considered, otherwise only the first one is taken into account.</param>
@@ -522,11 +533,21 @@ function hasVisibleBoundingBox() {
522533
}
523534
}";
524535
var polling = options.Visible || options.Hidden ? WaitForFunctionPollingOption.Raf : WaitForFunctionPollingOption.Mutation;
525-
var handle = await WaitForFunctionAsync(predicate, new WaitForFunctionOptions
526-
{
527-
Timeout = options.Timeout,
528-
Polling = polling
529-
}, selectorOrXPath, isXPath, options.Visible, options.Hidden);
536+
var handle = await new WaitTask(
537+
this,
538+
predicate,
539+
false,
540+
$"{(isXPath ? "XPath" : "selector")} '{selectorOrXPath}'",
541+
options.Polling,
542+
options.PollingInterval,
543+
options.Timeout,
544+
new object[]
545+
{
546+
selectorOrXPath,
547+
isXPath,
548+
options.Visible,
549+
options.Hidden
550+
}).Task;
530551
return handle as ElementHandle;
531552
}
532553

lib/PuppeteerSharp/Page.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ private Page(CDPSession client, Target target, FrameTree frameTree, bool ignoreH
146146
/// Raised when a frame is navigated to a new url.
147147
/// </summary>
148148
public event EventHandler<FrameEventArgs> FrameNavigated;
149-
149+
150150
/// <summary>
151151
/// Raised when a <see cref="Response"/> is received.
152152
/// </summary>
@@ -1220,7 +1220,7 @@ public Task WaitForTimeoutAsync(int milliseconds)
12201220
=> MainFrame.WaitForTimeoutAsync(milliseconds);
12211221

12221222
/// <summary>
1223-
/// Waits for a script to be evaluated to a truthy value
1223+
/// Waits for a function to be evaluated to a truthy value
12241224
/// </summary>
12251225
/// <param name="script">Function to be evaluated in browser context</param>
12261226
/// <param name="options">Optional waiting parameters</param>
@@ -1231,13 +1231,23 @@ public Task<JSHandle> WaitForFunctionAsync(string script, WaitForFunctionOptions
12311231
=> MainFrame.WaitForFunctionAsync(script, options ?? new WaitForFunctionOptions(), args);
12321232

12331233
/// <summary>
1234-
/// Waits for a script to be evaluated to a truthy value
1234+
/// Waits for a function to be evaluated to a truthy value
12351235
/// </summary>
12361236
/// <param name="script">Function to be evaluated in browser context</param>
12371237
/// <param name="args">Arguments to pass to <c>script</c></param>
12381238
/// <returns>A task that resolves when the <c>script</c> returns a truthy value</returns>
12391239
public Task<JSHandle> WaitForFunctionAsync(string script, params object[] args) => WaitForFunctionAsync(script, null, args);
12401240

1241+
/// <summary>
1242+
/// Waits for an expression to be evaluated to a truthy value
1243+
/// </summary>
1244+
/// <param name="script">Expression to be evaluated in browser context</param>
1245+
/// <param name="options">Optional waiting parameters</param>
1246+
/// <returns>A task that resolves when the <c>script</c> returns a truthy value</returns>
1247+
/// <seealso cref="Frame.WaitForExpressionAsync(string, WaitForFunctionOptions, object[])"/>
1248+
public Task<JSHandle> WaitForExpressionAsync(string script, WaitForFunctionOptions options = null)
1249+
=> MainFrame.WaitForExpressionAsync(script, options ?? new WaitForFunctionOptions());
1250+
12411251
/// <summary>
12421252
/// Waits for a selector to be added to the DOM
12431253
/// </summary>

lib/PuppeteerSharp/WaitForFunctionOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
/// </summary>
66
/// <seealso cref="Page.WaitForFunctionAsync(string, WaitForFunctionOptions, object[])"/>
77
/// <seealso cref="Frame.WaitForFunctionAsync(string, WaitForFunctionOptions, object[])"/>
8+
/// <seealso cref="WaitForSelectorOptions"/>
89
public class WaitForFunctionOptions
910
{
1011
/// <summary>

lib/PuppeteerSharp/WaitForSelectorOptions.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,8 @@
55
/// </summary>
66
/// <seealso cref="Page.WaitForSelectorAsync(string, WaitForSelectorOptions)"/>
77
/// <seealso cref="Frame.WaitForSelectorAsync(string, WaitForSelectorOptions)"/>
8-
public class WaitForSelectorOptions
8+
public class WaitForSelectorOptions : WaitForFunctionOptions
99
{
10-
/// <summary>
11-
/// Maximum time to wait for in milliseconds. Defaults to 30000 (30 seconds). Pass 0 to disable timeout.
12-
/// </summary>
13-
public int Timeout { get; set; } = 30_000;
14-
1510
/// <summary>
1611
/// Wait for selector to become visible.
1712
/// </summary>

lib/PuppeteerSharp/WaitTask.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ internal class WaitTask
1313
private readonly int? _pollingInterval;
1414
private readonly int _timeout;
1515
private readonly object[] _args;
16+
private readonly string _title;
1617
private readonly Task _timeoutTimer;
1718

1819
private readonly CancellationTokenSource _cts;
@@ -108,7 +109,15 @@ function onTimeout() {
108109
}
109110
}";
110111

111-
internal WaitTask(Frame frame, string predicateBody, WaitForFunctionPollingOption polling, int? pollingInterval, int timeout, object[] args)
112+
internal WaitTask(
113+
Frame frame,
114+
string predicateBody,
115+
bool isExpression,
116+
string title,
117+
WaitForFunctionPollingOption polling,
118+
int? pollingInterval,
119+
int timeout,
120+
object[] args = null)
112121
{
113122
if (string.IsNullOrEmpty(predicateBody))
114123
{
@@ -120,11 +129,12 @@ internal WaitTask(Frame frame, string predicateBody, WaitForFunctionPollingOptio
120129
}
121130

122131
_frame = frame;
123-
_predicateBody = $"return ( {predicateBody} )(...args)";
132+
_predicateBody = isExpression ? $"return {predicateBody}" : $"return ( {predicateBody} )(...args)";
124133
_polling = polling;
125134
_pollingInterval = pollingInterval;
126135
_timeout = timeout;
127-
_args = args;
136+
_args = args ?? new object[] { };
137+
_title = title;
128138

129139
frame.WaitTasks.Add(this);
130140
_taskCompletion = new TaskCompletionSource<JSHandle>();
@@ -134,15 +144,15 @@ internal WaitTask(Frame frame, string predicateBody, WaitForFunctionPollingOptio
134144
if (timeout > 0)
135145
{
136146
_timeoutTimer = System.Threading.Tasks.Task.Delay(timeout, _cts.Token).ContinueWith(_
137-
=> Termiante(new PuppeteerException($"waiting failed: timeout {timeout}ms exceeded")));
147+
=> Termiante(new WaitTaskTimeoutException(timeout, title)));
138148
}
139149

140150
Rerun();
141151
}
142152

143153
internal Task<JSHandle> Task => _taskCompletion.Task;
144154

145-
internal async void Rerun()
155+
internal async Task Rerun()
146156
{
147157
var runCount = ++_runCount;
148158
JSHandle success = null;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using System.Runtime.Serialization;
3+
4+
namespace PuppeteerSharp
5+
{
6+
[Serializable]
7+
internal class WaitTaskTimeoutException : PuppeteerException
8+
{
9+
/// <summary>
10+
/// Timeout that caused the exception
11+
/// </summary>
12+
/// <value>The timeout.</value>
13+
public int Timeout { get; }
14+
/// <summary>
15+
/// Element type the WaitTask was waiting for
16+
/// </summary>
17+
/// <value>The element.</value>
18+
public string ElementType { get; }
19+
20+
public WaitTaskTimeoutException()
21+
{
22+
}
23+
24+
public WaitTaskTimeoutException(string message) : base(message)
25+
{
26+
}
27+
28+
public WaitTaskTimeoutException(int timeout, string elementType) :
29+
base($"waiting for {elementType} failed: timeout {timeout}ms exceeded")
30+
{
31+
Timeout = timeout;
32+
ElementType = elementType;
33+
}
34+
35+
public WaitTaskTimeoutException(string message, Exception innerException) : base(message, innerException)
36+
{
37+
}
38+
39+
protected WaitTaskTimeoutException(SerializationInfo info, StreamingContext context) : base(info, context)
40+
{
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)