Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loading UI Interferes with Puppeteer navigation #52265

Closed
1 task done
ProchaLu opened this issue Jul 5, 2023 · 6 comments
Closed
1 task done

Loading UI Interferes with Puppeteer navigation #52265

ProchaLu opened this issue Jul 5, 2023 · 6 comments
Labels
area: app App directory (appDir: true) bug Issue was opened via the bug report template. locked Navigation Related to Next.js linking (e.g., <Link>) and navigation.

Comments

@ProchaLu
Copy link
Contributor

ProchaLu commented Jul 5, 2023

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
      Platform: darwin
      Arch: arm64
      Version: Darwin Kernel Version 22.1.0: Sun Oct  9 20:14:30 PDT 2022; root:xnu-8792.41.9~2/RELEASE_ARM64_T8103
    Binaries:
      Node: 18.16.0
      npm: 9.5.1
      Yarn: 1.22.19
      pnpm: 8.6.0
    Relevant Packages:
      next: 13.4.9-canary.2
      eslint-config-next: N/A
      react: 18.2.0
      react-dom: 18.2.0
      typescript: 5.1.3
    Next.js Config:
      output: N/A

Which area(s) of Next.js are affected? (leave empty if unsure)

App directory (appDir: true), Routing (next/router, next/navigation, next/link)

Link to the code that reproduces this issue or a replay of the bug

https://github.com/ProchaLu/next-route-puppeteer

To Reproduce

  1. Clone the Puppeteer test repository from GitHub
git clone https://github.com/ProchaLu/next-route-puppeteer-test
  1. Navigate to the project's root directory
cd next-route-puppeteer-test
  1. Install the project dependencies
  2. Start the Puppeteer test script by running the following command in the terminal:
node index.js

Describe the Bug

I have encountered an issue where the Next.js Loading component interferes with Puppeteer and the actual page loading process. Currently, Puppeteer considers the page as "done" when the Loading component is visible, causing a problem because Puppeteer waits for the network to become idle before proceeding.

To illustrate the problem, I have created a small Next.js app directory project with a Next.js Loading component that is visible for a short amount of time. The small project repository is deployed with vercel. Additionally, here is the Puppeteer test repository.

The problem seems to be that Next.js uses same-document navigation, causing the browser navigation to be resolved much earlier than the app-level navigation. Consequently, Puppeteer waits for the network to become idle, assuming the page has finished loading, while the actual app-level navigation and content loading are still in progress. This discrepancy causes issues between Puppeteer and the Next.js Loading component.

This issue is related to the problem described in the Puppeteer issue:

Expected Behavior

The expected behavior is for Puppeteer to accurately determine the readiness of the page, ensuring that it waits until the actual content is fully loaded before proceeding with the test. Ideally, there should be a seamless synchronization between Puppeteer's network idle state and the completion of the app-level navigation in Next.js. This improvement would prevent Puppeteer from misinterpreting the page readiness when the Next.js Loading component is displayed, resulting in consistent and reliable test execution.

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

Vercel

@ProchaLu ProchaLu added the bug Issue was opened via the bug report template. label Jul 5, 2023
@github-actions github-actions bot added area: app App directory (appDir: true) Navigation Related to Next.js linking (e.g., <Link>) and navigation. labels Jul 5, 2023
@ProchaLu
Copy link
Contributor Author

ProchaLu commented Jul 21, 2023

A workaround for this with Puppeteer and Next.js would be to use the page.waitForSelector() method, like in the example below:

  await Promise.all([
    page.waitForNavigation({ waitUntil: 'networkidle0' }),
    page.click('a[href="/page"]'),
  ]);

await page.waitForSelector('h1:has-text("page")');

@Summaw
Copy link

Summaw commented Jul 26, 2023

Adding onto what was said above from @ProchaLu you could set a timeout and maybe do a try catch so if selector is there do x if not then do y.

await page.waitForSelector('selectorhere', {timeout: 10 * 1000});

@ProchaLu
Copy link
Contributor Author

ProchaLu commented Aug 8, 2023

Another workaround would be to add a data-test-id to the loading component,

export default function RootLoading() {
  return <h1 data-test-id="loading">Loading</h1>;
}

This data-test-id is easy accessible and can be used in a page.waitForFunction to wait for the loading part to disappear from the page. Here's what the updated test looks like:

await Promise.all([
  page.waitForNavigation({ waitUntil: 'networkidle0' }),
  clickElement.click(),
]);

await page.waitForFunction(() => {
  const loadingElement = document.querySelector('[data-test-id="loading"]');
  return !loadingElement;
});

This change ensures that the test moves on only when the loading part is no longer there. This should address the problem of the loading component staying visible when we don't want it to.

@ProchaLu
Copy link
Contributor Author

ProchaLu commented Sep 22, 2023

I investigated the loading states in Next.js and found that loading state behavior differs in 3 ways in earlier versions (I tested 13.4.9 because the reproduction repos were already at this version) vs v13.4.20-canary.24 and later.

In some cases, with previous Next.js versions before v13.4.20-canary.24, the loading state:

  1. is not rendered when the network is pending
  2. is rendered when the network is idle
  3. is not rendered on a page with server-side timeout

To test the different behaviors, I created two reproduction projects (Next.js 13.4.9 reproduction, Next.js 13.4.20-canary.24 reproduction) using different Next.js versions with a loading state and a reproduction project without a loading state. I wrote a small Puppeteer test that goes to every page, checks if the loading state is visible, and waits for the network to be idle to see if this can cause an issue when the test runner thinks the page is already loaded. The reproduction projects contain:

  • Landing page featuring the cookies().get functionality
  • Page for the cookies().set operation
  • Test page to check the cookies().get function
  • Content-rich page with both text and image elements
  • Page with a client-side timeout of 2 seconds
  • Page with a server-side timeout of 2 seconds
  • Page equipped with a fetch function for interacting with an external API

Differences in loading state behavior in Next.js versions 13.4.9 and 13.4.20-canary.24 and in projects without an implemented loading state:

Behavior 1: Loading state not rendered when the network is pending

  • Next.js v13.4.9: The loading state is not rendered when making network requests with cookies().get() and cookies().set(), even though the network is pending
  • Next.js v13.4.20-canary.24: The loading state is rendered when making network requests with cookies().get() and cookies().set(), as expected

Behavior 2: Loading state is rendered when the network is idle

  • Next.js v13.4.9: The loading state is rendered when the network is idle, especially after a fetch request
  • Next.js v13.4.20-canary.24: The loading state is not rendered when the network is idle, as expected

Behavior 3: Loading state behavior when visiting a page with server-side timeout

  • Next.js v13.4.9: The loading state is not rendered, even though the network is pending
  • Next.js v13.4.20-canary.24: The loading state is rendered, as expected

No loading state in Next.js

Network requests are consistently completed before the page changes

Behavior 1: Loading state not rendered when the network is pending

Next.js v13.4.9 and loading state

The loading state is not rendered when making network requests with cookies().get() and cookies().set(), even though the network is pending. This is unexpected behavior.

nextjs-13-4-9-pending-no-loading.mov

Both screenshots show a pending network connection and no visible loading state.

old-network-pending-no-loading old-network-pending-test-no-loading

Next.js v13.4.20-canary.24 and loading state

The loading state is rendered when making network requests with cookies().get() and cookies().set(), as expected.

nextjs-13-4-20-cookie-loading.mov
new-network-pending-test-loading

Behavior 2: Loading state is rendered when the network is idle

Next.js v13.4.9 and loading state

The loading state is rendered when the network is idle, especially after a fetch request. This is unexpected behavior.

nextjs-13-4-9-fetch-loading.mov
old-network-idle-loading-state

Next.js v13.4.20-canary.24 and loading state

As expected, the loading state is not rendered when the network is idle.

nextjs-13-4-20-fetch-loading.mov
new-fetch-finished-no-loading

Behavior 3: Loading state behavior when visiting a page with server-side timeout

In this test case, I created a page with a server-side timeout to verify that the loading state is visible for a few seconds.

Next.js v13.4.9 and loading state

The loading state is not rendered, even though the network is pending. This is an unexpected behavior, as the loading state should be rendered whenever the network is pending.

nextjs-13-4-9-timeout.mov
old-network-pending-timeout-no-loading

Next.js v13.4.20-canary.24 and loading state

The loading state is rendered as expected.

nextjs-13-4-20-timout-loading.mov
new-network-pending-timeout-loading

No loading state in Next.js

In this reproduction repository without a Next.js loading state, I noticed network requests consistently complete before the page changes. This is the expected behavior, shown in the screenshots, where the network idles before the page change happens.

new-recording-no-loading.mov
  1. Network active on the previous page after click
no-loading-fetch-request
  1. Network is idle, and page changes
no-loading-fetch-idle

Other differences observed during testing

Next.js changed the network requests. After recording videos with Next.js v13.4.9 and v.13.4.20-canary.24, I observed a difference in network requests when deploying the same page.

Next.js v13.4.9, 10 requests

Screenshot 2023-09-22 at 12 24 13

Next.js v13.4.20-canary.24, 16 requests

Screenshot 2023-09-22 at 12 26 06

@ProchaLu
Copy link
Contributor Author

ProchaLu commented Sep 22, 2023

Solutions to the loading state issue with Next.js and Puppeteer

The loading state issue with Next.js and Puppeteer can be fixed by using a more robust way for a route change like OrKoN described in this comment. If you want to get into more detail, you can read my comment above.

Solution 1:

Use a more robust way for a route change, such as:

function clickAndWaitForNavigation(page) {
  await Promise.all([
    page.waitForNavigation({ waitUntil: 'networkidle0' }),
    page.click('a[href="/login"]'),
  ]);
  await page.waitForSelector('text/Welcome back, please login to your account');
}

Solution 2:

Use a data-test-id inside the loading.js file

export default function RootLoading() {
  return <div data-test-id="loading">...Loading...</div>;
}

Then, wait for the loading element to disappear in your test:

await Promise.all([
  page.waitForNavigation({ waitUntil: 'networkidle0' }),
  clickElement.click(),
]);

await page.waitForFunction(() => {
  const loadingElement = document.querySelector('[data-test-id="loading"]');
  return !loadingElement;
});

Solutions without using a loading state

When using no loading state in Next.js, you can use the Puppeteer function described in the docs.

const [response] = await Promise.all([
  page.waitForNavigation(waitOptions),
  page.click(selector, clickOptions),
]);

@github-actions
Copy link
Contributor

This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 17, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: app App directory (appDir: true) bug Issue was opened via the bug report template. locked Navigation Related to Next.js linking (e.g., <Link>) and navigation.
Projects
None yet
Development

No branches or pull requests

2 participants