Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -228,21 +228,6 @@
"FAIL"
]
},
{
"comment": "This is part of organizing the webdriver bidi implementation, We will remove it one by one",
"testIdPattern": "[navigation.spec] *timeout*",
"platforms": [
"darwin",
"linux",
"win32"
],
"parameters": [
"webDriverBiDi"
],
"expectations": [
"FAIL"
]
},
{
"comment": "This is part of organizing the webdriver bidi implementation, We will remove it one by one",
"testIdPattern": "[ariaqueryhandler.spec] *",
Expand Down
8 changes: 4 additions & 4 deletions lib/PuppeteerSharp.Tests/NavigationTests/PageGotoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ public void ShouldFailWhenExceedingMaximumNavigationTimeout()

var exception = Assert.ThrowsAsync<NavigationException>(async ()
=> await Page.GoToAsync(TestConstants.EmptyPage, new NavigationOptions { Timeout = 1 }));
Assert.That(exception.Message, Does.Contain("Timeout of 1 ms exceeded"));
Assert.That(exception.Message, Does.Contain("Navigation timeout of 1 ms exceeded"));
}

[Test, PuppeteerTest("navigation.spec", "navigation Page.goto", "should fail when exceeding default maximum navigation timeout")]
Expand All @@ -233,7 +233,7 @@ public void ShouldFailWhenExceedingDefaultMaximumNavigationTimeout()

Page.DefaultNavigationTimeout = 1;
var exception = Assert.ThrowsAsync<NavigationException>(async () => await Page.GoToAsync(TestConstants.EmptyPage));
Assert.That(exception.Message, Does.Contain("Timeout of 1 ms exceeded"));
Assert.That(exception.Message, Does.Contain("Navigation timeout of 1 ms exceeded"));
}

[Test, PuppeteerTest("navigation.spec", "navigation Page.goto", "should fail when exceeding default maximum timeout")]
Expand All @@ -243,7 +243,7 @@ public void ShouldFailWhenExceedingDefaultMaximumTimeout()
Server.SetRoute("/empty.html", _ => Task.Delay(-1));
Page.DefaultTimeout = 1;
var exception = Assert.ThrowsAsync<NavigationException>(async () => await Page.GoToAsync(TestConstants.EmptyPage));
Assert.That(exception.Message, Does.Contain("Timeout of 1 ms exceeded"));
Assert.That(exception.Message, Does.Contain("Navigation timeout of 1 ms exceeded"));
}

[Test, PuppeteerTest("navigation.spec", "navigation Page.goto", "should prioritize default navigation timeout over default timeout")]
Expand All @@ -254,7 +254,7 @@ public void ShouldPrioritizeDefaultNavigationTimeoutOverDefaultTimeout()
Page.DefaultTimeout = 0;
Page.DefaultNavigationTimeout = 1;
var exception = Assert.ThrowsAsync<NavigationException>(async () => await Page.GoToAsync(TestConstants.EmptyPage));
Assert.That(exception.Message, Does.Contain("Timeout of 1 ms exceeded"));
Assert.That(exception.Message, Does.Contain("Navigation timeout of 1 ms exceeded"));
}

[Test, PuppeteerTest("navigation.spec", "navigation Page.goto", "should disable timeout when its set to 0")]
Expand Down
4 changes: 2 additions & 2 deletions lib/PuppeteerSharp.Tests/PageTests/SetContentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public async Task ShouldRespectTimeout()
Timeout = 1
}));

Assert.That(exception!.Message, Does.Contain("Timeout of 1 ms exceeded"));
Assert.That(exception!.Message, Does.Contain("timeout of 1 ms exceeded"));
}

[Test, PuppeteerTest("page.spec", "Page Page.setContent", "should respect default navigation timeout")]
Expand All @@ -79,7 +79,7 @@ public async Task ShouldRespectDefaultTimeout()
var exception = Assert.ThrowsAsync<TimeoutException>(async () =>
await Page.SetContentAsync($"<img src='{TestConstants.ServerUrl + imgPath}'></img>"));

Assert.That(exception!.Message, Does.Contain("Timeout of 1 ms exceeded"));
Assert.That(exception!.Message, Does.Contain("timeout of 1 ms exceeded"));
}

[Test, PuppeteerTest("page.spec", "Page Page.setContent", "should await resources to load")]
Expand Down
4 changes: 3 additions & 1 deletion lib/PuppeteerSharp/Bidi/BidiFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ public override async Task<IResponse> GoToAsync(string url, NavigationOptions op

try
{
await Task.WhenAll(waitForNavigationTask, navigationTask).ConfigureAwait(false);
// - If any task fails/is canceled, immediately throw (don't wait for other tasks)
// - If all tasks succeed, return when all have completed
await new[] { waitForNavigationTask, navigationTask }.WhenAllFailFast().ConfigureAwait(false);
}
catch (NavigationException)
{
Expand Down
5 changes: 4 additions & 1 deletion lib/PuppeteerSharp/Cdp/LifecycleWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ public LifecycleWatcher(

public CdpHttpResponse NavigationResponse => (CdpHttpResponse)_navigationRequest?.Response;

public Task TerminationTask => _terminationTaskWrapper.Task.WithTimeout(_timeout, cancellationToken: _terminationCancellationToken.Token);
public Task TerminationTask => _terminationTaskWrapper.Task.WithTimeout(
_timeout,
t => new TimeoutException($"Navigation timeout of {t.TotalMilliseconds} ms exceeded"),
_terminationCancellationToken.Token);

public Task LifecycleTask => _lifecycleTaskWrapper.Task;

Expand Down
30 changes: 30 additions & 0 deletions lib/PuppeteerSharp/Helpers/TaskHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -188,6 +189,35 @@ public static async Task<T> WithTimeout<T>(this Task<T> task, TimeSpan timeout,
return await task.ConfigureAwait(false);
}

/// <summary>
/// Awaits all tasks, but fails immediately if any task fails (like JavaScript's Promise.all).
/// Unlike <see cref="Task.WhenAll(Task[])"/> which waits for all tasks to complete before throwing,
/// this method throws immediately when any task fails or is canceled.
/// </summary>
/// <param name="tasks">The tasks to await.</param>
/// <returns>A task that completes when all tasks complete successfully.</returns>
public static async Task WhenAllFailFast(this Task[] tasks)
{
if (tasks == null)
{
throw new ArgumentNullException(nameof(tasks));
}

var remaining = new HashSet<Task>(tasks);

while (remaining.Count > 0)
{
var completed = await Task.WhenAny(remaining).ConfigureAwait(false);
remaining.Remove(completed);

// If the task failed or was canceled, throw immediately (don't wait for others)
if (completed.IsFaulted || completed.IsCanceled)
{
await completed.ConfigureAwait(false);
}
}
}

private static async Task<bool> TimeoutTask(Task task, TimeSpan timeout)
{
if (timeout <= TimeSpan.Zero)
Expand Down
Loading