Skip to content

Commit 2b71f5a

Browse files
Meir017kblok
authored andcommitted
Implement Page.waitFor (#147)
1 parent 3c379fd commit 2b71f5a

File tree

9 files changed

+395
-9
lines changed

9 files changed

+395
-9
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,28 @@ using (var page = await Browser.NewPageAsync())
7171
}
7272
```
7373

74+
## Wait For Selector
75+
76+
```cs
77+
using (var page = await Browser.NewPageAsync())
78+
{
79+
await page.GoToAsync("http://www.spapage.com");
80+
await page.WaitForSelectorAsync("div.main-content")
81+
await page.PdfAsync(outputFile));
82+
}
83+
```
84+
85+
## Wait For Function
86+
```cs
87+
using (var page = await Browser.NewPageAsync())
88+
{
89+
await page.GoToAsync("http://www.spapage.com");
90+
var watchDog = page.WaitForFunctionAsync("window.innerWidth < 100");
91+
await Page.SetViewport(new ViewPortOptions { Width = 50, Height = 50 });
92+
await watchDog;
93+
}
94+
```
95+
7496
# Monthly reports
7597
* [April 2018](http://www.hardkoded.com/blogs/puppeteer-sharp-monthly-april-2018)
7698
* [March 2018](http://www.hardkoded.com/blogs/puppeteer-sharp-monthly-march-2018)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Xunit;
4+
5+
namespace PuppeteerSharp.Tests.Page
6+
{
7+
[Collection("PuppeteerLoaderFixture collection")]
8+
public class WaitForTests : PuppeteerPageBaseTest
9+
{
10+
[Fact]
11+
public async Task ShouldWaitForSelector()
12+
{
13+
var found = false;
14+
var waitFor = Page.WaitForSelectorAsync("div").ContinueWith(_ => found = true);
15+
await Page.GoToAsync(TestConstants.EmptyPage);
16+
17+
Assert.False(found);
18+
19+
await Page.GoToAsync(TestConstants.ServerUrl + "/grid.html");
20+
await waitFor;
21+
Assert.True(found);
22+
}
23+
24+
[Fact]
25+
public async Task ShouldTimeout()
26+
{
27+
var startTime = DateTime.Now;
28+
var timeout = 42;
29+
await Page.WaitForTimeoutAsync(timeout);
30+
Assert.True((DateTime.Now - startTime).TotalMilliseconds > timeout / 2);
31+
}
32+
33+
[Fact]
34+
public async Task ShouldWaitForPredicate()
35+
{
36+
var watchdog = Page.WaitForFunctionAsync("() => window.innerWidth < 100");
37+
Page.SetViewport(new ViewPortOptions { Width = 10, Height = 10 });
38+
await watchdog;
39+
}
40+
41+
[Fact]
42+
public async Task ShouldWaitForPredicateWithArguments()
43+
{
44+
await Page.WaitForFunctionAsync("(arg1, arg2) => arg1 !== arg2", new WaitForFunctionOptions(), 1, 2);
45+
}
46+
}
47+
}

lib/PuppeteerSharp.Tests/Puppeteer/PuppeteerLaunchTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public async Task ShouldWorkInRealLifeWithOptions()
5050
"https://www.google.com",
5151
new NavigationOptions()
5252
{
53-
Timeout = 5000,
53+
Timeout = 10000,
5454
WaitUntil = new[] { WaitUntilNavigation.Networkidle0 }
5555
});
5656
Assert.Equal(response.Status, HttpStatusCode.OK);

lib/PuppeteerSharp/Frame.cs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ public class Frame
1313
private string _defaultContextId = "<not-initialized>";
1414
private object _context = null;
1515
private string _url = string.Empty;
16-
private List<WaitTask> _waitTasks;
1716
private bool _detached;
1817
private TaskCompletionSource<ElementHandle> _documentCompletionSource;
1918

19+
internal List<WaitTask> WaitTasks { get; }
20+
2021
public Frame(Session client, Page page, Frame parentFrame, string frameId)
2122
{
2223
_client = client;
@@ -31,7 +32,7 @@ public Frame(Session client, Page page, Frame parentFrame, string frameId)
3132

3233
SetDefaultContext(null);
3334

34-
_waitTasks = new List<WaitTask>();
35+
WaitTasks = new List<WaitTask>();
3536
LifecycleEvents = new List<string>();
3637
}
3738

@@ -163,7 +164,7 @@ internal void SetDefaultContext(ExecutionContext context)
163164
{
164165
ContextResolveTaskWrapper.SetResult(context);
165166

166-
foreach (var waitTask in _waitTasks)
167+
foreach (var waitTask in WaitTasks)
167168
{
168169
waitTask.Rerun();
169170
}
@@ -176,7 +177,7 @@ internal void SetDefaultContext(ExecutionContext context)
176177

177178
internal void Detach()
178179
{
179-
foreach (var waitTask in _waitTasks)
180+
foreach (var waitTask in WaitTasks)
180181
{
181182
waitTask.Termiante(new Exception("waitForSelector failed: frame got detached."));
182183
}
@@ -188,10 +189,43 @@ internal void Detach()
188189
_parentFrame = null;
189190
}
190191

192+
internal Task WaitForTimeoutAsync(int milliseconds) => Task.Delay(milliseconds);
193+
194+
internal Task<JSHandle> WaitForFunctionAsync(string script, WaitForFunctionOptions options, params object[] args)
195+
=> new WaitTask(this, script, options.Polling, options.Timeout, args).Task;
196+
197+
internal async Task<ElementHandle> WaitForSelectorAsync(string selector, WaitForSelectorOptions options)
198+
{
199+
const string predicate = @"
200+
function predicate(selector, waitForVisible, waitForHidden) {
201+
const node = document.querySelector(selector);
202+
if (!node)
203+
return waitForHidden;
204+
if (!waitForVisible && !waitForHidden)
205+
return node;
206+
const style = window.getComputedStyle(node);
207+
const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
208+
const success = (waitForVisible === isVisible || waitForHidden === !isVisible);
209+
return success ? node : null;
210+
211+
function hasVisibleBoundingBox() {
212+
const rect = node.getBoundingClientRect();
213+
return !!(rect.top || rect.bottom || rect.width || rect.height);
214+
}
215+
}";
216+
var polling = options.Visible || options.Hidden ? WaitForFunctionPollingOption.Raf : WaitForFunctionPollingOption.Mutation;
217+
var handle = await WaitForFunctionAsync(predicate, new WaitForFunctionOptions
218+
{
219+
Timeout = options.Timeout,
220+
Polling = polling
221+
}, selector, options.Visible, options.Hidden);
222+
return handle.AsElement();
223+
}
224+
191225
#endregion
192226

193227
#region Private Methods
194-
228+
195229
private async Task<ElementHandle> GetDocument()
196230
{
197231
if (_documentCompletionSource == null)

lib/PuppeteerSharp/Page.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,41 @@ await Task.WhenAll(
541541
return navigationTask.Result;
542542
}
543543

544+
/// <summary>
545+
/// Waits for a timeout
546+
/// </summary>
547+
/// <param name="milliseconds"></param>
548+
/// <returns>A task that resolves when after the timeout</returns>
549+
public Task WaitForTimeoutAsync(int milliseconds)
550+
=> MainFrame.WaitForTimeoutAsync(milliseconds);
551+
552+
/// <summary>
553+
/// Waits for a script to be evaluated to a truthy value
554+
/// </summary>
555+
/// <param name="script">Function to be evaluated in browser context</param>
556+
/// <param name="options">Optional waiting parameters</param>
557+
/// <param name="args">Arguments to pass to <c>script</c></param>
558+
/// <returns>A task that resolves when the <c>script</c> returns a truthy value</returns>
559+
public Task<JSHandle> WaitForFunctionAsync(string script, WaitForFunctionOptions options = null, params object[] args)
560+
=> MainFrame.WaitForFunctionAsync(script, options ?? new WaitForFunctionOptions(), args);
561+
562+
/// <summary>
563+
/// Waits for a script to be evaluated to a truthy value
564+
/// </summary>
565+
/// <param name="script">Function to be evaluated in browser context</param>
566+
/// <param name="args">Arguments to pass to <c>script</c></param>
567+
/// <returns>A task that resolves when the <c>script</c> returns a truthy value</returns>
568+
public Task<JSHandle> WaitForFunctionAsync(string script, params object[] args) => WaitForFunctionAsync(script, null, args);
569+
570+
/// <summary>
571+
/// Waits for a selector to be added to the DOM
572+
/// </summary>
573+
/// <param name="selector">A selector of an element to wait for</param>
574+
/// <param name="options">Optional waiting parameters</param>
575+
/// <returns>A task that resolves when element specified by selector string is added to DOM</returns>
576+
public Task<ElementHandle> WaitForSelectorAsync(string selector, WaitForSelectorOptions options = null)
577+
=> MainFrame.WaitForSelectorAsync(selector, options ?? new WaitForSelectorOptions());
578+
544579
#endregion
545580

546581
#region Private Method
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace PuppeteerSharp
2+
{
3+
/// <summary>
4+
/// Optional waiting parameters.
5+
/// </summary>
6+
/// <seealso cref="Page.WaitForFunctionAsync(string, WaitForFunctionOptions, object[])"/>
7+
/// <seealso cref="Frame.WaitForFunctionAsync(string, WaitForFunctionOptions, object[])"/>
8+
public class WaitForFunctionOptions
9+
{
10+
/// <summary>
11+
/// Maximum time to wait for in milliseconds. Defaults to 30000 (30 seconds).
12+
/// </summary>
13+
public int Timeout { get; set; } = 30_000;
14+
15+
/// <summary>
16+
/// An interval at which the <c>pageFunction</c> is executed. defaults to <see cref="WaitForFunctionPollingOption.Raf"/>
17+
/// </summary>
18+
public WaitForFunctionPollingOption Polling { get; set; } = WaitForFunctionPollingOption.Raf;
19+
}
20+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using Newtonsoft.Json;
2+
using Newtonsoft.Json.Converters;
3+
4+
namespace PuppeteerSharp
5+
{
6+
/// <summary>
7+
/// An interval at which the <c>pageFunction</c> is executed.
8+
/// </summary>
9+
[JsonConverter(typeof(StringEnumConverter), true)]
10+
public enum WaitForFunctionPollingOption
11+
{
12+
/// <summary>
13+
/// To constantly execute <c>pageFunction</c> in <c>requestAnimationFrame</c> callback.
14+
/// This is the tightest polling mode which is suitable to observe styling changes.
15+
/// </summary>
16+
Raf,
17+
/// <summary>
18+
/// To execute <c>pageFunction</c> on every DOM mutation.
19+
/// </summary>
20+
Mutation
21+
}
22+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace PuppeteerSharp
2+
{
3+
/// <summary>
4+
/// Optional waiting parameters.
5+
/// </summary>
6+
/// <seealso cref="Page.WaitForSelectorAsync(string, WaitForSelectorOptions)"/>
7+
/// <seealso cref="Frame.WaitForSelectorAsync(string, WaitForSelectorOptions)"/>
8+
public class WaitForSelectorOptions
9+
{
10+
/// <summary>
11+
/// Maximum time to wait for in milliseconds. Defaults to 30000 (30 seconds).
12+
/// </summary>
13+
public int Timeout { get; set; } = 30_000;
14+
15+
/// <summary>
16+
/// Wait for selector to become visible.
17+
/// </summary>
18+
public bool Visible { get; set; }
19+
20+
/// <summary>
21+
/// Wait for selector to become hidden.
22+
/// </summary>
23+
public bool Hidden { get; set; }
24+
}
25+
}

0 commit comments

Comments
 (0)