Skip to content

Commit 37ef53e

Browse files
Meir017kblok
authored andcommitted
Implement Frame.waitForFunction (#157)
1 parent 5858143 commit 37ef53e

File tree

4 files changed

+96
-4
lines changed

4 files changed

+96
-4
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Xunit;
4+
5+
namespace PuppeteerSharp.Tests.Frame
6+
{
7+
[Collection("PuppeteerLoaderFixture collection")]
8+
public class WaitForFunctionTests : PuppeteerPageBaseTest
9+
{
10+
[Fact]
11+
public async Task ShouldPollOnInterval()
12+
{
13+
var success = false;
14+
var startTime = DateTime.Now;
15+
var polling = 100;
16+
var watchdog = Page.WaitForFunctionAsync("() => window.__FOO === 'hit'", new WaitForFunctionOptions { PollingInterval = polling })
17+
.ContinueWith(_ => success = true);
18+
await Page.EvaluateExpressionAsync("window.__FOO = 'hit'");
19+
Assert.False(success);
20+
await Page.EvaluateExpressionAsync("document.body.appendChild(document.createElement('div'))");
21+
await watchdog;
22+
Assert.True((DateTime.Now - startTime).TotalMilliseconds > polling / 2);
23+
}
24+
25+
[Fact]
26+
public async Task ShouldPollOnMutation()
27+
{
28+
var success = false;
29+
var watchdog = Page.WaitForFunctionAsync("() => window.__FOO === 'hit'",
30+
new WaitForFunctionOptions { Polling = WaitForFunctionPollingOption.Mutation })
31+
.ContinueWith(_ => success = true);
32+
await Page.EvaluateExpressionAsync("window.__FOO = 'hit'");
33+
Assert.False(success);
34+
await Page.EvaluateExpressionAsync("document.body.appendChild(document.createElement('div'))");
35+
await watchdog;
36+
}
37+
38+
[Fact]
39+
public async Task ShouldPollOnRaf()
40+
{
41+
var watchdog = Page.WaitForFunctionAsync("() => window.__FOO === 'hit'",
42+
new WaitForFunctionOptions { Polling = WaitForFunctionPollingOption.Raf });
43+
await Page.EvaluateExpressionAsync("window.__FOO = 'hit'");
44+
await watchdog;
45+
}
46+
47+
[Fact]
48+
public async Task ShouldThrowNegativePollingInterval()
49+
{
50+
var exception = await Assert.ThrowsAsync<ArgumentOutOfRangeException>(()
51+
=> Page.WaitForFunctionAsync("() => !!document.body", new WaitForFunctionOptions { PollingInterval = -10 }));
52+
53+
Assert.Contains("Cannot poll with non-positive interval", exception.Message);
54+
}
55+
56+
[Fact]
57+
public async Task ShouldReturnTheSuccessValueAsAJSHandle()
58+
{
59+
Assert.Equal(5, await (await Page.WaitForFunctionAsync("() => 5")).JsonValue<int>());
60+
}
61+
62+
[Fact]
63+
public async Task ShouldReturnTheWindowAsASuccessValue()
64+
{
65+
Assert.NotNull(await Page.WaitForFunctionAsync("() => window"));
66+
}
67+
68+
[Fact]
69+
public async Task ShouldAcceptElementHandleArguments()
70+
{
71+
await Page.SetContentAsync("<div></div>");
72+
var div = await Page.GetElementAsync("div");
73+
var resolved = false;
74+
var waitForFunction = Page.WaitForFunctionAsync("element => !element.parentElement", div)
75+
.ContinueWith(_ => resolved = true);
76+
Assert.False(resolved);
77+
await Page.EvaluateFunctionAsync("element => element.remove()", div);
78+
await waitForFunction;
79+
}
80+
}
81+
}

lib/PuppeteerSharp/Frame.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ internal void Detach()
192192
internal Task WaitForTimeoutAsync(int milliseconds) => Task.Delay(milliseconds);
193193

194194
internal Task<JSHandle> WaitForFunctionAsync(string script, WaitForFunctionOptions options, params object[] args)
195-
=> new WaitTask(this, script, options.Polling, options.Timeout, args).Task;
195+
=> new WaitTask(this, script, options.Polling, options.PollingInterval, options.Timeout, args).Task;
196196

197197
internal async Task<ElementHandle> WaitForSelectorAsync(string selector, WaitForSelectorOptions options)
198198
{

lib/PuppeteerSharp/WaitForFunctionOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,10 @@ public class WaitForFunctionOptions
1616
/// An interval at which the <c>pageFunction</c> is executed. defaults to <see cref="WaitForFunctionPollingOption.Raf"/>
1717
/// </summary>
1818
public WaitForFunctionPollingOption Polling { get; set; } = WaitForFunctionPollingOption.Raf;
19+
20+
/// <summary>
21+
/// An interval at which the <c>pageFunction</c> is executed. If no value is specified will use <see cref="Polling"/>
22+
/// </summary>
23+
public int? PollingInterval { get; set; }
1924
}
2025
}

lib/PuppeteerSharp/WaitTask.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ internal class WaitTask
1010
private readonly Frame _frame;
1111
private readonly string _predicateBody;
1212
private readonly WaitForFunctionPollingOption _polling;
13+
private readonly int? _pollingInterval;
1314
private readonly int _timeout;
1415
private readonly object[] _args;
1516
private readonly Task _timeoutTimer;
@@ -107,24 +108,29 @@ function onTimeout() {
107108
}
108109
}";
109110

110-
internal WaitTask(Frame frame, string predicateBody, WaitForFunctionPollingOption polling, int timeout, object[] args)
111+
internal WaitTask(Frame frame, string predicateBody, WaitForFunctionPollingOption polling, int? pollingInterval, int timeout, object[] args)
111112
{
112113
if (string.IsNullOrEmpty(predicateBody))
113114
{
114115
throw new ArgumentNullException(nameof(predicateBody));
115116
}
117+
if (pollingInterval <= 0)
118+
{
119+
throw new ArgumentOutOfRangeException(nameof(pollingInterval), "Cannot poll with non-positive interval");
120+
}
116121

117122
_frame = frame;
118123
_predicateBody = $"return ( {predicateBody} )(...args)";
119124
_polling = polling;
125+
_pollingInterval = pollingInterval;
120126
_timeout = timeout;
121127
_args = args;
122128

123129
frame.WaitTasks.Add(this);
124130
_taskCompletion = new TaskCompletionSource<JSHandle>();
125131

126132
_cts = new CancellationTokenSource();
127-
133+
128134
_timeoutTimer = System.Threading.Tasks.Task.Delay(timeout, _cts.Token).ContinueWith(_
129135
=> Termiante(new PuppeteerException($"waiting failed: timeout {timeout}ms exceeded")));
130136

@@ -143,7 +149,7 @@ internal async void Rerun()
143149
try
144150
{
145151
success = await context.EvaluateFunctionHandleAsync(WaitForPredicatePageFunction,
146-
new object[] { _predicateBody, _polling, _timeout }.Concat(_args).ToArray());
152+
new object[] { _predicateBody, _pollingInterval ?? (object)_polling, _timeout }.Concat(_args).ToArray());
147153
}
148154
catch (Exception ex)
149155
{

0 commit comments

Comments
 (0)