Skip to content

Run continuation asynchronously in context of events#3256

Open
nvborisenko wants to merge 21 commits intomicrosoft:mainfrom
nvborisenko:continuation-context
Open

Run continuation asynchronously in context of events#3256
nvborisenko wants to merge 21 commits intomicrosoft:mainfrom
nvborisenko:continuation-context

Conversation

@nvborisenko
Copy link
Copy Markdown

This PR make Playwright events waiter more friendly with sync-over-async pattern.

Issue

This piece of code demonstrates deadlock. Real code, you can run it (I ran it in NUnit).

await page.RunAndWaitForResponseAsync(async () =>
{
    await page.GotoAsync("https://linked.in");
}, new Regex(".*google.*"));

//await Task.Yield();

Task.Run(async () => await page.Locator(".nav__logo-link").ClickAsync()).GetAwaiter().GetResult();

Here GetAwaiter().GetResult() is blocked. We understand that it is anti-pattern, sync-over-async. To fix it we can add await Task.Yield(). But what if Playwright can be more friendly for dummy users like me?

Solution?

This PR allows tasks continuations asynchronously. Now the code above just works.

@nvborisenko
Copy link
Copy Markdown
Author

Apologies for the inquiry, but after a month without activity, I'm concerned I may have done something improperly. @mxschmitt, if you happen to know the cause, I would be very grateful for a hint. Thank you.

@Skn0tt
Copy link
Copy Markdown
Member

Skn0tt commented Dec 18, 2025

Hi Nikolay, sorry for not taking a look earlier. I'll ask our colleague Dima for a review, but it'll take a little longer due to holidays.

@Skn0tt Skn0tt requested a review from dgozman December 18, 2025 10:35
Copy link
Copy Markdown
Member

@Skn0tt Skn0tt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some digging on the ramifications of this options, and it seems like a safe change for us. The only negative implication is a slight overhead since the continuation now needs to wait for the scheduler, but that microsecond impact is neglegible given we're dealing with slow browsers.

My understanding of DotNet's async scheduling is limited, so please take another look @dgozman.

@nvborisenko
Copy link
Copy Markdown
Author

It is absolutely safe, moreover it is recommended for libraries to prevent deadlocks I demonstrated in the original issue.

Copy link
Copy Markdown
Collaborator

@dgozman dgozman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix looks good to me. Let's add a test though, to avoid regressing this behavior.

@nvborisenko
Copy link
Copy Markdown
Author

Honestly, I am not sure how to test it properly. Please advise.

@dgozman
Copy link
Copy Markdown
Collaborator

dgozman commented Jan 12, 2026

Honestly, I am not sure how to test it properly. Please advise.

Add a test next to this one that follows your repro code from this issue. I'd recommend to use this test page, so that you can wait for **/style.css response.

@nvborisenko
Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@nvborisenko
Copy link
Copy Markdown
Author

Added test, verified locally - it blocks in main branch, and green in this branch. Please review again.

@nvborisenko
Copy link
Copy Markdown
Author

CI is unstable with the message Target page, context or browser has been closed. I hope it is not related to this PR.

@dgozman
Copy link
Copy Markdown
Collaborator

dgozman commented Jan 30, 2026

Thank you for the PR!

@Skn0tt
Copy link
Copy Markdown
Member

Skn0tt commented Jan 30, 2026

Hmm, CI is still failing after trying again. @nvborisenko, could you merge in main to see if this is also true on Playwright 1.58? Maybe it was fixed upstream.

@nvborisenko
Copy link
Copy Markdown
Author

Stabilized CI build. Actually original simple changes/improvements revealed hidden race conditions. Then I made some changes to "fix" it, but I am afraid some of these "improvements" might be unnecessary (I don't fully understand what I did).

I will try to identify whether my changes are actually necessary to clean up this PR at least. Thank you for your support.

Copy link
Copy Markdown
Member

@Skn0tt Skn0tt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking into it! I'll mark this as "request changes" to prevent anybody from accidentally merging while we haven't re-reviewed it.

@nvborisenko
Copy link
Copy Markdown
Author

Current status: I blame tests are flaky only those who depend on external website. I think my initial commit should be enough. My next step is to ignore all "external" tests and see whether CI is stable.

@nvborisenko
Copy link
Copy Markdown
Author

@Skn0tt @dgozman there are no changes required, everything is minimal and even better. Tests are flaky by design. I walked through the history and observed flakiness even on green builds:
https://github.com/microsoft/playwright-dotnet/actions/runs/23795732235/job/69342390543

WARNING: Test Microsoft.Playwright.Tests.BrowserContextEventsTests.DialogEventShouldWork needed 3 retries and passed
  Passed DialogEventShouldWork [30 s]

Why it is happening... Let's see concrete example

        var task = Page.EvaluateAsync(@"() => {
            window.open('javascript:prompt(""hey?"")');
        }");

        var dialog = await WaitForContextDialog(Page.Context);

So the flow is evaluate -> prompt opened -> start awaiting. This is misuse, I would say bad. The correct flow should: start awaiting -> evaluate -> prompt opened.

We can easy verify it, the following code always fails:

        var task = Page.EvaluateAsync(@"() => {
            window.open('javascript:prompt(""hey?"")');
        }");

        await Task.Delay(100); // 100 ms between actual event and awaiter

        var dialog = await WaitForContextDialog(Page.Context);

My changes made the "open window" shorter, what is very good in terms of performance. But now, I also improved this area to minimize race condition: register the events handler as soon as possible and then do unnecessary stuff. So it helped stabilize the CI. Anyway correct API is RunAndWait*.

So I am promoting the changes in this PR to be merged. Please let me know what is needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants