Skip to content

Commit edb38f5

Browse files
Meir017kblok
authored andcommitted
Updated Page.waitFor & Frame.waitFor tests (#329)
1 parent ed7d1b4 commit edb38f5

File tree

5 files changed

+224
-33
lines changed

5 files changed

+224
-33
lines changed

lib/PuppeteerSharp.Tests/FrameTests/WaitForSelectorTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,14 @@ public async Task ShouldRunInSpecifiedFrame()
8383
var frame1 = Page.Frames.ElementAt(1);
8484
var frame2 = Page.Frames.ElementAt(2);
8585
var added = false;
86-
var selectorTask = frame2.WaitForSelectorAsync("div").ContinueWith(_ => added = true);
86+
var waitForSelectorPromise = frame2.WaitForSelectorAsync("div").ContinueWith(_ => added = true);
8787
Assert.False(added);
8888

8989
await frame1.EvaluateFunctionAsync(AddElement, "div");
9090
Assert.False(added);
9191

9292
await frame2.EvaluateFunctionAsync(AddElement, "div");
93-
Assert.True(added);
93+
await waitForSelectorPromise;
9494
}
9595

9696
[Fact]
@@ -113,7 +113,7 @@ public async Task ShouldThrowWhenFrameIsDetached()
113113
await FrameUtils.DetachFrameAsync(Page, "frame1");
114114
var waitException = await waitTask;
115115
Assert.NotNull(waitException);
116-
Assert.Contains("waitForSelector failed: frame got detached", waitException.Message);
116+
Assert.Contains("waitForFunction failed: frame got detached.", waitException.Message);
117117
}
118118

119119
[Fact]
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Xunit;
4+
using Xunit.Abstractions;
5+
6+
namespace PuppeteerSharp.Tests.FrameTests
7+
{
8+
[Collection("PuppeteerLoaderFixture collection")]
9+
public class WaitForXPathTests : PuppeteerPageBaseTest
10+
{
11+
const string addElement = "tag => document.body.appendChild(document.createElement(tag))";
12+
13+
public WaitForXPathTests(ITestOutputHelper output) : base(output)
14+
{
15+
}
16+
17+
[Fact]
18+
public async Task ShouldSupportSomeFancyXpath()
19+
{
20+
await Page.SetContentAsync("<p>red herring</p><p>hello world </p>");
21+
var waitForXPath = Page.WaitForXPathAsync("//p[normalize-space(.)=\"hello world\"]");
22+
Assert.Equal("hello world ", await Page.EvaluateFunctionAsync("x => x.textContent", await waitForXPath));
23+
}
24+
25+
[Fact]
26+
public async Task ShouldRunInSpecifiedFrame()
27+
{
28+
await FrameUtils.AttachFrameAsync(Page, "frame1", TestConstants.EmptyPage);
29+
await FrameUtils.AttachFrameAsync(Page, "frame2", TestConstants.EmptyPage);
30+
var frame1 = Page.Frames[1];
31+
var frame2 = Page.Frames[2];
32+
var added = false;
33+
var waitForXPathPromise = frame2.WaitForXPathAsync("//div").ContinueWith(_ => added = true);
34+
Assert.False(added);
35+
await frame1.EvaluateFunctionAsync(addElement, "div");
36+
Assert.False(added);
37+
await frame2.EvaluateFunctionAsync(addElement, "div");
38+
await waitForXPathPromise;
39+
}
40+
41+
[Fact]
42+
public async Task ShouldThrowIfEvaluationFailed()
43+
{
44+
await Page.EvaluateOnNewDocumentAsync(@"function() {
45+
document.evaluate = null;
46+
}");
47+
await Page.GoToAsync(TestConstants.EmptyPage);
48+
var exception = await Assert.ThrowsAsync<EvaluationFailedException>(()
49+
=> Page.WaitForXPathAsync("*"));
50+
Assert.Contains("document.evaluate is not a function", exception.Message);
51+
}
52+
53+
[Fact]
54+
public async Task ShouldThrowWhenFrameIsDetached()
55+
{
56+
await FrameUtils.AttachFrameAsync(Page, "frame1", TestConstants.EmptyPage);
57+
var frame = Page.Frames[1];
58+
var waitPromise = frame.WaitForXPathAsync("//*[@class=\"box\"]");
59+
await FrameUtils.DetachFrameAsync(Page, "frame1");
60+
var exception = await Assert.ThrowsAnyAsync<Exception>(() => waitPromise);
61+
Assert.Contains("waitForFunction failed: frame got detached.", exception.Message);
62+
}
63+
64+
[Fact]
65+
public async Task HiddenShouldWaitForDisplayNone()
66+
{
67+
var divHidden = false;
68+
await Page.SetContentAsync("<div style='display: block;'></div>");
69+
var waitForXPath = Page.WaitForXPathAsync("//div", new WaitForSelectorOptions { Hidden = true })
70+
.ContinueWith(_ => divHidden = true);
71+
await Page.WaitForXPathAsync("//div"); // do a round trip
72+
Assert.False(divHidden);
73+
await Page.EvaluateExpressionAsync("document.querySelector('div').style.setProperty('display', 'none')");
74+
Assert.True(await waitForXPath);
75+
Assert.True(divHidden);
76+
}
77+
78+
[Fact]
79+
public async Task ShouldReturnTheElementHandle()
80+
{
81+
var waitForXPath = Page.WaitForXPathAsync("//*[@class=\"zombo\"]");
82+
await Page.SetContentAsync("<div class='zombo'>anything</div>");
83+
Assert.Equal("anything", await Page.EvaluateFunctionAsync<string>("x => x.textContent", await waitForXPath));
84+
}
85+
86+
[Fact]
87+
public async Task ShouldAllowYouToSelectATextNode()
88+
{
89+
await Page.SetContentAsync("<div>some text</div>");
90+
var text = await Page.WaitForXPathAsync("//div/text()");
91+
Assert.Equal(3 /* Node.TEXT_NODE */, await (await text.GetPropertyAsync("nodeType")).JsonValueAsync<int>());
92+
}
93+
94+
[Fact]
95+
public async Task ShouldAllowYouToSelectAnElementWithSingleSlash()
96+
{
97+
await Page.SetContentAsync("<div>some text</div>");
98+
var waitForXPath = Page.WaitForXPathAsync("/html/body/div");
99+
Assert.Equal("some text", await Page.EvaluateFunctionAsync<string>("x => x.textContent", await waitForXPath));
100+
}
101+
}
102+
}

lib/PuppeteerSharp.Tests/PageTests/WaitForTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,27 @@ public async Task ShouldWaitForSelector()
2626
Assert.True(found);
2727
}
2828

29+
[Fact]
30+
public async Task ShouldWaitForAnXpath()
31+
{
32+
var found = false;
33+
var waitFor = Page.WaitForXPathAsync("//div").ContinueWith(_ => found = true);
34+
await Page.GoToAsync(TestConstants.EmptyPage);
35+
Assert.False(found);
36+
await Page.GoToAsync(TestConstants.ServerUrl + "/grid.html");
37+
await waitFor;
38+
Assert.True(found);
39+
}
40+
41+
[Fact]
42+
public async Task ShouldNotAllowYouToSelectAnElementWithSingleSlashXpath()
43+
{
44+
await Page.SetContentAsync("<div>some text</div>");
45+
var exception = await Assert.ThrowsAsync<EvaluationFailedException>(() =>
46+
Page.WaitForSelectorAsync("/html/body/div"));
47+
Assert.NotNull(exception);
48+
}
49+
2950
[Fact]
3051
public async Task ShouldTimeout()
3152
{

lib/PuppeteerSharp/Frame.cs

Lines changed: 66 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -219,36 +219,40 @@ public async Task<JSHandle> EvaluateFunctionHandleAsync(string function, params
219219
/// <param name="selector">A selector of an element to wait for</param>
220220
/// <param name="options">Optional waiting parameters</param>
221221
/// <returns>A task that resolves when element specified by selector string is added to DOM</returns>
222+
/// <seealso cref="WaitForXPathAsync(string, WaitForSelectorOptions)"/>
222223
/// <seealso cref="Page.WaitForSelectorAsync(string, WaitForSelectorOptions)"/>
223-
public async Task<ElementHandle> WaitForSelectorAsync(string selector, WaitForSelectorOptions options = null)
224-
{
225-
options = options ?? new WaitForSelectorOptions();
226-
const string predicate = @"
227-
function predicate(selector, waitForVisible, waitForHidden) {
228-
const node = document.querySelector(selector);
229-
if (!node)
230-
return waitForHidden;
231-
if (!waitForVisible && !waitForHidden)
232-
return node;
233-
const style = window.getComputedStyle(node);
234-
const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
235-
const success = (waitForVisible === isVisible || waitForHidden === !isVisible);
236-
return success ? node : null;
237-
238-
function hasVisibleBoundingBox() {
239-
const rect = node.getBoundingClientRect();
240-
return !!(rect.top || rect.bottom || rect.width || rect.height);
241-
}
242-
}";
243-
var polling = options.Visible || options.Hidden ? WaitForFunctionPollingOption.Raf : WaitForFunctionPollingOption.Mutation;
244-
var handle = await WaitForFunctionAsync(predicate, new WaitForFunctionOptions
245-
{
246-
Timeout = options.Timeout,
247-
Polling = polling
248-
}, selector, options.Visible, options.Hidden);
249-
return handle as ElementHandle;
250-
}
224+
public Task<ElementHandle> WaitForSelectorAsync(string selector, WaitForSelectorOptions options = null)
225+
=> WaitForSelectorOrXPathAsync(selector, false, options);
251226

227+
/// <summary>
228+
/// Waits for a selector to be added to the DOM
229+
/// </summary>
230+
/// <param name="xpath">A xpath selector of an element to wait for</param>
231+
/// <param name="options">Optional waiting parameters</param>
232+
/// <returns>A task that resolves when element specified by selector string is added to DOM</returns>
233+
/// <example>
234+
/// <code>
235+
/// <![CDATA[
236+
/// var browser = await Puppeteer.LaunchAsync(new LaunchOptions(), Downloader.DefaultRevision);
237+
/// var page = await browser.NewPageAsync();
238+
/// string currentURL = null;
239+
/// page.MainFrame
240+
/// .WaitForXPathAsync("//img")
241+
/// .ContinueWith(_ => Console.WriteLine("First URL with image: " + currentURL));
242+
/// foreach (var current in new[] { "https://example.com", "https://google.com", "https://bbc.com" })
243+
/// {
244+
/// currentURL = current;
245+
/// await page.GoToAsync(currentURL);
246+
/// }
247+
/// await browser.CloseAsync();
248+
/// ]]>
249+
/// </code>
250+
/// </example>
251+
/// <seealso cref="WaitForSelectorAsync(string, WaitForSelectorOptions)"/>
252+
/// <seealso cref="Page.WaitForXPathAsync(string, WaitForSelectorOptions)"/>
253+
public Task<ElementHandle> WaitForXPathAsync(string xpath, WaitForSelectorOptions options = null)
254+
=> WaitForSelectorOrXPathAsync(xpath, true, options);
255+
252256
/// <summary>
253257
/// Waits for a timeout
254258
/// </summary>
@@ -486,6 +490,39 @@ public Task SetContentAsync(string html)
486490
/// <seealso cref="Page.GetTitleAsync"/>
487491
public Task<string> GetTitleAsync() => EvaluateExpressionAsync<string>("document.title");
488492

493+
internal async Task<ElementHandle> WaitForSelectorOrXPathAsync(string selectorOrXPath, bool isXPath, WaitForSelectorOptions options = null)
494+
{
495+
options = options ?? new WaitForSelectorOptions();
496+
const string predicate = @"
497+
function predicate(selectorOrXPath, isXPath, waitForVisible, waitForHidden) {
498+
const node = isXPath
499+
? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
500+
: document.querySelector(selectorOrXPath);
501+
if (!node)
502+
return waitForHidden;
503+
if (!waitForVisible && !waitForHidden)
504+
return node;
505+
const element = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
506+
507+
const style = window.getComputedStyle(element);
508+
const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
509+
const success = (waitForVisible === isVisible || waitForHidden === !isVisible);
510+
return success ? node : null;
511+
512+
function hasVisibleBoundingBox() {
513+
const rect = element.getBoundingClientRect();
514+
return !!(rect.top || rect.bottom || rect.width || rect.height);
515+
}
516+
}";
517+
var polling = options.Visible || options.Hidden ? WaitForFunctionPollingOption.Raf : WaitForFunctionPollingOption.Mutation;
518+
var handle = await WaitForFunctionAsync(predicate, new WaitForFunctionOptions
519+
{
520+
Timeout = options.Timeout,
521+
Polling = polling
522+
}, selectorOrXPath, isXPath, options.Visible, options.Hidden);
523+
return handle as ElementHandle;
524+
}
525+
489526
internal void OnLifecycleEvent(string loaderId, string name)
490527
{
491528
if (name == "init")
@@ -524,7 +561,7 @@ internal void Detach()
524561
{
525562
while (WaitTasks.Count > 0)
526563
{
527-
WaitTasks[0].Termiante(new Exception("waitForSelector failed: frame got detached."));
564+
WaitTasks[0].Termiante(new Exception("waitForFunction failed: frame got detached."));
528565
}
529566
Detached = true;
530567
if (ParentFrame != null)

lib/PuppeteerSharp/Page.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ public Task<JSHandle> QuerySelectorAllHandleAsync(string selector)
328328
/// </remarks>
329329
/// <seealso cref="Frame.XPathAsync(string)"/>
330330
public Task<ElementHandle[]> XPathAsync(string expression) => MainFrame.XPathAsync(expression);
331-
331+
332332
/// <summary>
333333
/// Executes a script in browser context
334334
/// </summary>
@@ -1237,9 +1237,40 @@ public Task<JSHandle> WaitForFunctionAsync(string script, WaitForFunctionOptions
12371237
/// <param name="selector">A selector of an element to wait for</param>
12381238
/// <param name="options">Optional waiting parameters</param>
12391239
/// <returns>A task that resolves when element specified by selector string is added to DOM</returns>
1240+
/// <seealso cref="WaitForXPathAsync(string, WaitForSelectorOptions)"/>
1241+
/// <seealso cref="Frame.WaitForSelectorAsync(string, WaitForSelectorOptions)"/>
12401242
public Task<ElementHandle> WaitForSelectorAsync(string selector, WaitForSelectorOptions options = null)
12411243
=> MainFrame.WaitForSelectorAsync(selector, options ?? new WaitForSelectorOptions());
12421244

1245+
/// <summary>
1246+
/// Waits for a xpath selector to be added to the DOM
1247+
/// </summary>
1248+
/// <param name="xpath">A xpath selector of an element to wait for</param>
1249+
/// <param name="options">Optional waiting parameters</param>
1250+
/// <returns>A task that resolves when element specified by selector string is added to DOM</returns>
1251+
/// <example>
1252+
/// <code>
1253+
/// <![CDATA[
1254+
/// var browser = await Puppeteer.LaunchAsync(new LaunchOptions(), Downloader.DefaultRevision);
1255+
/// var page = await browser.NewPageAsync();
1256+
/// string currentURL = null;
1257+
/// page
1258+
/// .WaitForXPathAsync("//img")
1259+
/// .ContinueWith(_ => Console.WriteLine("First URL with image: " + currentURL));
1260+
/// foreach (var current in new[] { "https://example.com", "https://google.com", "https://bbc.com" })
1261+
/// {
1262+
/// currentURL = current;
1263+
/// await page.GoToAsync(currentURL);
1264+
/// }
1265+
/// await browser.CloseAsync();
1266+
/// ]]>
1267+
/// </code>
1268+
/// </example>
1269+
/// <seealso cref="WaitForSelectorAsync(string, WaitForSelectorOptions)"/>
1270+
/// <seealso cref="Frame.WaitForXPathAsync(string, WaitForSelectorOptions)"/>
1271+
public Task<ElementHandle> WaitForXPathAsync(string xpath, WaitForSelectorOptions options = null)
1272+
=> MainFrame.WaitForXPathAsync(xpath, options ?? new WaitForSelectorOptions());
1273+
12431274
/// <summary>
12441275
/// Waits for navigation
12451276
/// </summary>

0 commit comments

Comments
 (0)