microsoft / playwright-dotnet

.NET version of the Playwright testing and automation library.
https://playwright.dev/dotnet/
MIT License
2.47k stars 235 forks source link

[Question] Switching to new window failing intermittently #1953

Closed davidhprotective closed 2 years ago

davidhprotective commented 2 years ago

I am using PW with .Net - xUnit. I am currently working on a Login test where the homepage I navigate to automatically pops-up Okta sign-in window. I need to switch to that window and enter username/password and then switch back to the main window. (Okta window closes automatically after sign in).

After navigating to the homepage, I am trying to add the new pop-up window to Context and switch to it with the below code:

public IPage AddSignInWindowToContextAndSwitchToIt(IBrowserContext context)
        {
            IPage signInWindow = null;

            //Attempt count to switch to pop up sign in window
            int counter = 0;

            while (signInWindow == null && counter < 6)
            {
                ++counter;
                context.Page += async (_, page) =>
                {
                    page.WaitForLoadStateAsync().Wait();
                    Console.WriteLine(await page.TitleAsync());
                };

                signInWindow = SwitchToSignInWindow(context.Pages);
                Thread.Sleep(1000);
            }
            return signInWindow;
        }

        public IPage SwitchToSignInWindow(IReadOnlyList<IPage> pages)
        {
            foreach (var page in pages)
            {
                if (page.TitleAsync().Result.Contains("Sign in to your account"))
                {
                    return page;
                }
            }
            return null;
        }

I pass BrowserContext instance from the Test class that calls this method.

[Collection("Functional Test Collection")]
    public class LoginTests : IDisposable
    {
        private readonly LoginPageDriver _pageDriver;
        private readonly TestConfiguration _config;
        private readonly FunctionalTestFixture _fixture;
        private readonly IBrowserContext _context;

        public LoginTests(FunctionalTestFixture fixture)
        {
            this._fixture = fixture;
            this._context = this._fixture.NewContext();
            this._pageDriver = new LoginPageDriver(this._context);
            this._config = new TestConfiguration();
        }

        [Fact]
        public void VerifyLogin()
        {
            _pageDriver.NavigateTo(_config.UiBaseUrl);
            var title = _pageDriver.GetTitle();
            Assert.Equal("Dashboard Experience", title);
            var signInWindow = _pageDriver.AddSignInWindowToContextAndSwitchToIt(this._context);
            _pageDriver.CompleteOktaSignIn(signInWindow, _config.Auth.TestUserName, _config.Auth.TestUserPassword);
            var mainWindow = _pageDriver.SwitchToMainWindow(this._context.Pages);
            var visibility = _pageDriver.IsSearchBoxVisibleAsync(mainWindow);
            Assert.True(visibility, "Search Input not Visible!");
        }

        public void Dispose()
        {
            _context?.DisposeAsync();
        }
    }

And here is the TestFixture that runs before All Tests:

using System;
using System.Threading.Tasks;
using Microsoft.Playwright;

namespace AcceptanceTests.Fixtures
{
    public class FunctionalTestFixture : IDisposable
    {
        private readonly IPlaywright _playwright;
        private readonly IBrowser _browser;

        public FunctionalTestFixture()
        {
            _playwright = Playwright.CreateAsync().Result;
            _browser = _playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
                {
                    Headless = false
                }).Result;
        }

        public IBrowserContext NewContext()
        {
            return this._browser.NewContextAsync().Result;
        }

        public void Dispose()
        {
            Task.Run(() => _browser.DisposeAsync()).Wait();
            _playwright.Dispose();
        }
    }
}

When I run tests with this setup, I intermittently get below error for VerifyLogin() test:

---- System.AggregateException : One or more errors occurred. (Execution context was destroyed, most likely because of a navigation)

----- Inner Stack Trace -----
   at Microsoft.Playwright.Transport.Connection.SendMessageToServerAsync[T](String guid, String method, Object args)
   at Microsoft.Playwright.Transport.Channels.FrameChannel.TitleAsync()
----- Inner Stack Trace microsoft/playwright#2 (Xunit.Sdk.TestClassException) -----

The source of the context was destroyed error is pointing to the foreach loop above.

Any suggestions what I might be doing wrong here?

mxschmitt commented 2 years ago

In Playwright there is not something called "switching to a window", you can just use a specific page or frame and it works. Instead of using Thread.Sleep(1000); I would recommend using await Page.WaitForTimeout(1000) otherwise the events are not coming in.

What you probably want is BrowserContext.WaitForPage see here: https://playwright.dev/dotnet/docs/next/api/class-browsercontext#browser-context-wait-for-page

davidhprotective commented 2 years ago

I tried using BrowserContext.WaitForPage but it times out waiting for the page when indeed a new page has automatically opened/popped out already. Does it try to create a new Page itself? In my case, navigating to home page already creates a new pop up window/page.

My question is how can I switch the control of Page object to the newly popped up sign in window so that I can fill in the username/password input fields there?

mxschmitt commented 2 years ago

Can you provide us a repro? We have a plenty of tests for it and its working for us. For example here:

https://github.com/microsoft/playwright-dotnet/blob/93bb3842a712bbd1374e5375aef6ec68bc1cfd6a/src/Playwright.Tests/BrowserContextNetworkEventTests.cs#L47

davidhprotective commented 2 years ago

In my case, there is no click or any action happening for the pop-up window to open. Navigating to home page automatically pops up a new login window. In this case, how do I get control (or point Page instance to) the newly popped up page? Your code seems to say wait for the page to make a click and get the instance of the new page.

mxschmitt commented 2 years ago

You can also listen for new Pages on the context with the Page event: context.Page += (_, page) => ...

Its not possible to re-point to a different page instance, you most likely just want to get the new page instance.