From 37ef53e3fd0e9fbbd52b55086af9e6dab9b810b6 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Fri, 20 Apr 2018 17:57:26 +0300 Subject: [PATCH] Implement Frame.waitForFunction (#157) --- .../Frame/WaitForFunctionTests.cs | 81 +++++++++++++++++++ lib/PuppeteerSharp/Frame.cs | 2 +- lib/PuppeteerSharp/WaitForFunctionOptions.cs | 5 ++ lib/PuppeteerSharp/WaitTask.cs | 12 ++- 4 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 lib/PuppeteerSharp.Tests/Frame/WaitForFunctionTests.cs diff --git a/lib/PuppeteerSharp.Tests/Frame/WaitForFunctionTests.cs b/lib/PuppeteerSharp.Tests/Frame/WaitForFunctionTests.cs new file mode 100644 index 000000000..be08072be --- /dev/null +++ b/lib/PuppeteerSharp.Tests/Frame/WaitForFunctionTests.cs @@ -0,0 +1,81 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace PuppeteerSharp.Tests.Frame +{ + [Collection("PuppeteerLoaderFixture collection")] + public class WaitForFunctionTests : PuppeteerPageBaseTest + { + [Fact] + public async Task ShouldPollOnInterval() + { + var success = false; + var startTime = DateTime.Now; + var polling = 100; + var watchdog = Page.WaitForFunctionAsync("() => window.__FOO === 'hit'", new WaitForFunctionOptions { PollingInterval = polling }) + .ContinueWith(_ => success = true); + await Page.EvaluateExpressionAsync("window.__FOO = 'hit'"); + Assert.False(success); + await Page.EvaluateExpressionAsync("document.body.appendChild(document.createElement('div'))"); + await watchdog; + Assert.True((DateTime.Now - startTime).TotalMilliseconds > polling / 2); + } + + [Fact] + public async Task ShouldPollOnMutation() + { + var success = false; + var watchdog = Page.WaitForFunctionAsync("() => window.__FOO === 'hit'", + new WaitForFunctionOptions { Polling = WaitForFunctionPollingOption.Mutation }) + .ContinueWith(_ => success = true); + await Page.EvaluateExpressionAsync("window.__FOO = 'hit'"); + Assert.False(success); + await Page.EvaluateExpressionAsync("document.body.appendChild(document.createElement('div'))"); + await watchdog; + } + + [Fact] + public async Task ShouldPollOnRaf() + { + var watchdog = Page.WaitForFunctionAsync("() => window.__FOO === 'hit'", + new WaitForFunctionOptions { Polling = WaitForFunctionPollingOption.Raf }); + await Page.EvaluateExpressionAsync("window.__FOO = 'hit'"); + await watchdog; + } + + [Fact] + public async Task ShouldThrowNegativePollingInterval() + { + var exception = await Assert.ThrowsAsync(() + => Page.WaitForFunctionAsync("() => !!document.body", new WaitForFunctionOptions { PollingInterval = -10 })); + + Assert.Contains("Cannot poll with non-positive interval", exception.Message); + } + + [Fact] + public async Task ShouldReturnTheSuccessValueAsAJSHandle() + { + Assert.Equal(5, await (await Page.WaitForFunctionAsync("() => 5")).JsonValue()); + } + + [Fact] + public async Task ShouldReturnTheWindowAsASuccessValue() + { + Assert.NotNull(await Page.WaitForFunctionAsync("() => window")); + } + + [Fact] + public async Task ShouldAcceptElementHandleArguments() + { + await Page.SetContentAsync("
"); + var div = await Page.GetElementAsync("div"); + var resolved = false; + var waitForFunction = Page.WaitForFunctionAsync("element => !element.parentElement", div) + .ContinueWith(_ => resolved = true); + Assert.False(resolved); + await Page.EvaluateFunctionAsync("element => element.remove()", div); + await waitForFunction; + } + } +} diff --git a/lib/PuppeteerSharp/Frame.cs b/lib/PuppeteerSharp/Frame.cs index d9270b200..5d4e6d70d 100644 --- a/lib/PuppeteerSharp/Frame.cs +++ b/lib/PuppeteerSharp/Frame.cs @@ -192,7 +192,7 @@ internal void Detach() internal Task WaitForTimeoutAsync(int milliseconds) => Task.Delay(milliseconds); internal Task WaitForFunctionAsync(string script, WaitForFunctionOptions options, params object[] args) - => new WaitTask(this, script, options.Polling, options.Timeout, args).Task; + => new WaitTask(this, script, options.Polling, options.PollingInterval, options.Timeout, args).Task; internal async Task WaitForSelectorAsync(string selector, WaitForSelectorOptions options) { diff --git a/lib/PuppeteerSharp/WaitForFunctionOptions.cs b/lib/PuppeteerSharp/WaitForFunctionOptions.cs index 9f6904aa2..bfa54c84f 100644 --- a/lib/PuppeteerSharp/WaitForFunctionOptions.cs +++ b/lib/PuppeteerSharp/WaitForFunctionOptions.cs @@ -16,5 +16,10 @@ public class WaitForFunctionOptions /// An interval at which the pageFunction is executed. defaults to /// public WaitForFunctionPollingOption Polling { get; set; } = WaitForFunctionPollingOption.Raf; + + /// + /// An interval at which the pageFunction is executed. If no value is specified will use + /// + public int? PollingInterval { get; set; } } } \ No newline at end of file diff --git a/lib/PuppeteerSharp/WaitTask.cs b/lib/PuppeteerSharp/WaitTask.cs index 8a842c9b1..0d86e63c6 100644 --- a/lib/PuppeteerSharp/WaitTask.cs +++ b/lib/PuppeteerSharp/WaitTask.cs @@ -10,6 +10,7 @@ internal class WaitTask private readonly Frame _frame; private readonly string _predicateBody; private readonly WaitForFunctionPollingOption _polling; + private readonly int? _pollingInterval; private readonly int _timeout; private readonly object[] _args; private readonly Task _timeoutTimer; @@ -107,16 +108,21 @@ function onTimeout() { } }"; - internal WaitTask(Frame frame, string predicateBody, WaitForFunctionPollingOption polling, int timeout, object[] args) + internal WaitTask(Frame frame, string predicateBody, WaitForFunctionPollingOption polling, int? pollingInterval, int timeout, object[] args) { if (string.IsNullOrEmpty(predicateBody)) { throw new ArgumentNullException(nameof(predicateBody)); } + if (pollingInterval <= 0) + { + throw new ArgumentOutOfRangeException(nameof(pollingInterval), "Cannot poll with non-positive interval"); + } _frame = frame; _predicateBody = $"return ( {predicateBody} )(...args)"; _polling = polling; + _pollingInterval = pollingInterval; _timeout = timeout; _args = args; @@ -124,7 +130,7 @@ internal WaitTask(Frame frame, string predicateBody, WaitForFunctionPollingOptio _taskCompletion = new TaskCompletionSource(); _cts = new CancellationTokenSource(); - + _timeoutTimer = System.Threading.Tasks.Task.Delay(timeout, _cts.Token).ContinueWith(_ => Termiante(new PuppeteerException($"waiting failed: timeout {timeout}ms exceeded"))); @@ -143,7 +149,7 @@ internal async void Rerun() try { success = await context.EvaluateFunctionHandleAsync(WaitForPredicatePageFunction, - new object[] { _predicateBody, _polling, _timeout }.Concat(_args).ToArray()); + new object[] { _predicateBody, _pollingInterval ?? (object)_polling, _timeout }.Concat(_args).ToArray()); } catch (Exception ex) {