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

jsdom screenshot gets stuck #268

Closed
pvieira91 opened this issue Jul 8, 2020 · 6 comments
Closed

jsdom screenshot gets stuck #268

pvieira91 opened this issue Jul 8, 2020 · 6 comments
Labels
needs:reproduction scope:node Related to MSW running in Node

Comments

@pvieira91
Copy link

pvieira91 commented Jul 8, 2020

Hi,

I've been using this tool to mock some api responses. It worked nicely until I tried to generate screenshots using the jsdom-screenshot. In the process of generating an image, JSDom launches a server on-the-fly (running on a random port). I found out that my tests were getting stuck on the generateImage step.

The following request never gets a response:
http://127.0.0.1:64667/devtools/browser/7c081340-c14d-4d6b-a8ed-7b3a8ba2fc2e
This looks like a request made by puppeteer. (devtools protocol)

Based on my search, msw should proxy pass all requests meaning that this shouldn't happen.

Here's the generateimage script from jsdom-screenshot
https://github.com/dferber90/jsdom-screenshot/blob/master/generateImage.js

A last note to thank you for this fantastic work.

Regards

PS: As a quick workaround, I'm closing the server before the step of image generating - instead of doing it on the afterAll. This means that I also need to server.listen before every test. Is that okay? Is there a method a more lightweight method to achieve that?

@kettanaito
Copy link
Member

Hey, @pvieira91. Thanks for reaching out.

Please, does your test pass without being stuck when you remove API mocking?
Also, could you include two essential parts:

  • How do you use setupServer?
  • How do you make an actual request? (fetch(...)/axios(...))

@kettanaito kettanaito added needs:reproduction scope:node Related to MSW running in Node labels Jul 9, 2020
@pvieira91
Copy link
Author

@kettanaito

  • The app uses fetch to perform the requests. As NodeJS does not support fetch I'm using "node-fetch" and setting that to global.
import fetch from "node-fetch"
global.fetch = fetch;
  • I was able to workaround it by calling server.close before the generateImage (from jsdom-screenshot);

  • I was calling setupServer once in the top of the file. Due to the way i decided to workaround it, I'm now calling the setup server in every test.

test("it should verify the report page", async () => {
    const server = setupServer(...apiHandlers);
    server.listen();

    const { container } = await renderPracticeTest(reportRoute);
    const pageObject = createPracticeTestPO(container);

    server.close();
    await waitForElementToBeRemoved(() => pageObject.reportTabContent.loader);

    expect(pageObject.reportTabContent.reportsTable).toBeVisible();
    // this is a utlity method wich calls the generateImage from jsdom-screenshot
    await addToVisualRegression({ width: 1280, height: 1500 }); 
  });
// apiHandlers
const handlers =  rest.get("https://cpa-qa.becker.com/rest/practiceTests", (req, res, ctx) => {
    return res(ctx.json(SomeJsonWithTheResponse1));
  }),
  rest.get("https://cpa-qa.becker.com/rest/adaptiveSessions/sectionPracticeTests", (req, res, ctx) => {
    return res(ctx.json(SomeJsonWithTheResponse2));
  }),
rest.get("https://cpa-qa.becker.com/rest/section/2000019/questionIds", (req, res, ctx) => {
    return res(ctx.json(SomeJsonWithTheResponse3));
  })

@kettanaito
Copy link
Member

Thanks for providing the insights. I don't see anything that would have caused a screenshot generation to hang.

I've been tackling with an issue that would result in a POST request with a body to hang a request forever in Node (#275) recently. That may be the case if jsdom-screenshot performs a POST request to a temporarily established endpoint via http module. One would have to dive into how that library is implemented to find out.

Could you please set up a minimal reproduction repository for this issue?

@kettanaito
Copy link
Member

I have a suspicion to believe that the issue happens due to the combination of how jsdom-screenshot works and some yet unknown factor. Per jsdom-screenshot explanation section:

Once the server is read, it launches a puppeteer instance and opens that index.html page. It waits until all resources are loaded (the network becomes idle) before taking a screenshot.

Most likely it hangs forever because the network is never idle and there is some pending request. I don't know how the NodeJS request interception can affect Puppeteer's runtime, to be honest, as it only intercepts requests in the current process (Puppeteer spawns Chromium in a new process, plus, it's a browser environment).

In my experience such hanging may be caused by improper handling of the Service Worker. Do you establish any client-side interception as a part of renderPracticeTest/your test suite?

@msutkowski
Copy link
Member

Hello @pvieira91! I did a little research into this earlier and set up a working repo here: https://github.com/msutkowski/msw-jsdom-snapshot-repro

The highlights are that you need to currently change the launch options to use pipe: true to tell puppeteer to not use a WebSocket. The WS seems to be getting intercepted and not passed through correctly, which causes the chromium instance to 'hang'. Please note: this is just a guesstimation and will need more research to get to the root cause. I'm not personally familiar enough with all of the lower-level interactions between msw/node, node-request-interceptor, puppeteer, and http - but hopefully, @kettanaito or another can help resolve this.

As an example, you can eliminate the server.close() in the individual tests and instead use the globals like this if you desire:

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

it("should have no visual regressions while rendering the greeting", async () => {
  const { getByTestId } = render(<App />);
  await waitFor(() => getByTestId("greeting"));

  expect(getByTestId("greeting")).toHaveTextContent("hello, superbanana");

  expect(
    await generateImage()
  ).toMatchImageSnapshot();
});

Let me know if you have any questions, and I hope this helps you out and saves some time :)

@kettanaito
Copy link
Member

Puppeteer attempts to establish a WebSocket connection to its instance to make a screenshot. That connection (request) gets intercepted by node-request-interceptor and since we don't have a WebSocket support (#156), it cannot handle it properly.

Please consider launching your Puppeteer instance with pipe: true option, as @msutkowski has rightfully discovered.

// src/setupTests.js
import { setDefaultOptions } from "jsdom-screenshot";

setDefaultOptions({
  launch: {
    pipe: true,
  },
});

By setting pipe to true you configure Puppeteer to use IPC instead of WebSocket to communicate with a launched browser instance.

Once we have a WebSocket support you should be safe to omit this option.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs:reproduction scope:node Related to MSW running in Node
Projects
None yet
Development

No branches or pull requests

3 participants