Skip to content

Commit 92cc6c7

Browse files
bdurranikblok
authored andcommitted
Browser.waitForTarget (#762)
1 parent 5c7cc1a commit 92cc6c7

File tree

4 files changed

+102
-1
lines changed

4 files changed

+102
-1
lines changed

lib/PuppeteerSharp.Tests/BrowserContextTests/BrowserContextTests.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,5 +149,27 @@ public async Task ShouldWorkAcrossSessions()
149149
remoteBrowser.Disconnect();
150150
await context.CloseAsync();
151151
}
152+
153+
[Fact]
154+
public async Task ShouldWaitForTarget()
155+
{
156+
var context = await Browser.CreateIncognitoBrowserContextAsync();
157+
var targetPromise = context.WaitForTargetAsync((target) => target.Url == TestConstants.EmptyPage);
158+
var page = await context.NewPageAsync();
159+
await page.GoToAsync(TestConstants.EmptyPage);
160+
var promiseTarget = await targetPromise;
161+
var targetPage = await promiseTarget.PageAsync();
162+
Assert.Equal(targetPage, page);
163+
await context.CloseAsync();
164+
}
165+
166+
[Fact]
167+
public async Task ShouldTimeoutWaitingForNonExistantTarget()
168+
{
169+
var context = await Browser.CreateIncognitoBrowserContextAsync();
170+
var exception = await Assert.ThrowsAsync<TimeoutException>(()
171+
=> context.WaitForTargetAsync((target) => target.Url == TestConstants.EmptyPage, new WaitForOptions { Timeout = 1 }));
172+
await context.CloseAsync();
173+
}
152174
}
153175
}

lib/PuppeteerSharp.Tests/TargetTests/TargetTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ await Task.WhenAll(
181181
Server.WaitForRequest("/one-style.css")
182182
);
183183
// Connect to the opened page.
184-
var target = Browser.Targets().First(t => t.Url.Contains("one-style.html"));
184+
var target = await Context.WaitForTargetAsync(t => t.Url.Contains("one-style.html"));
185185
var newPage = await target.PageAsync();
186186
// Issue a redirect.
187187
serverResponse.Redirect("/injectedstyle.css");

lib/PuppeteerSharp/Browser.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ public Browser(
149149
internal Connection Connection { get; }
150150
internal ViewPortOptions DefaultViewport { get; }
151151

152+
/// <summary>
153+
/// Dafault wait time in milliseconds. Defaults to 30 seconds.
154+
/// </summary>
155+
public int DefaultWaitForTimeout { get; set; } = 30000;
156+
152157
#endregion
153158

154159
#region Public Methods
@@ -258,6 +263,63 @@ public async Task<string> GetUserAgentAsync()
258263
/// <returns>Task</returns>
259264
public Task CloseAsync() => _closeTask ?? (_closeTask = CloseCoreAsync());
260265

266+
/// <summary>
267+
/// This searches for a target in this specific browser context.
268+
/// <example>
269+
/// <code>
270+
/// <![CDATA[
271+
/// await page.EvaluateAsync("() => window.open('https://www.example.com/')");
272+
/// var newWindowTarget = await browserContext.WaitForTargetAsync((target) => target.Url == "https://www.example.com/");
273+
/// ]]>
274+
/// </code>
275+
/// </example>
276+
/// </summary>
277+
/// <param name="predicate">A function to be run for every target</param>
278+
/// <param name="options">options</param>
279+
/// <returns>Resolves to the first target found that matches the predicate function.</returns>
280+
public async Task<Target> WaitForTargetAsync(Func<Target, bool> predicate, WaitForOptions options = null)
281+
{
282+
var timeout = options?.Timeout ?? DefaultWaitForTimeout;
283+
var existingTarget = Targets().FirstOrDefault(predicate);
284+
if (existingTarget != null)
285+
{
286+
return existingTarget;
287+
}
288+
289+
var targetCompletionSource = new TaskCompletionSource<Target>();
290+
291+
void TargetHandler(object sender, TargetChangedArgs e)
292+
{
293+
if (predicate(e.Target))
294+
{
295+
targetCompletionSource.TrySetResult(e.Target);
296+
}
297+
}
298+
299+
try
300+
{
301+
TargetCreated += TargetHandler;
302+
TargetChanged += TargetHandler;
303+
304+
var task = await Task.WhenAny(new[]
305+
{
306+
TaskHelper.CreateTimeoutTask(timeout),
307+
targetCompletionSource.Task
308+
}).ConfigureAwait(false);
309+
310+
// if this was the timeout task, this will throw a timeout exception
311+
// othewise this is the targetCompletionSource task which has already
312+
// completed
313+
await task;
314+
return await targetCompletionSource.Task;
315+
}
316+
finally
317+
{
318+
TargetCreated -= TargetHandler;
319+
TargetChanged -= TargetHandler;
320+
}
321+
}
322+
261323
private async Task CloseCoreAsync()
262324
{
263325
try

lib/PuppeteerSharp/BrowserContext.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,23 @@ internal BrowserContext(IConnection connection, Browser browser, string contextI
5555
/// </summary>
5656
/// <returns>An array of all active targets inside the browser context</returns>
5757
public Target[] Targets() => Array.FindAll(Browser.Targets(), target => target.BrowserContext == this);
58+
59+
/// <summary>
60+
/// This searches for a target in this specific browser context.
61+
/// <example>
62+
/// <code>
63+
/// <![CDATA[
64+
/// await page.EvaluateAsync("() => window.open('https://www.example.com/')");
65+
/// var newWindowTarget = await browserContext.WaitForTargetAsync((target) => target.Url == "https://www.example.com/");
66+
/// ]]>
67+
/// </code>
68+
/// </example>
69+
/// </summary>
70+
/// <param name="predicate">A function to be run for every target</param>
71+
/// <param name="options">options</param>
72+
/// <returns>Resolves to the first target found that matches the predicate function.</returns>
73+
public Task<Target> WaitForTargetAsync(Func<Target, bool> predicate, WaitForOptions options = null)
74+
=> Browser.WaitForTargetAsync((target) => target.BrowserContext == this && predicate(target), options);
5875

5976
/// <summary>
6077
/// An array of all pages inside the browser context.

0 commit comments

Comments
 (0)