CheshireCaat / playwright-with-fingerprints

Anonymous automation via playwright with fingerprint replacement technology.
MIT License
138 stars 10 forks source link

Sample Code Hangs / Doesn't return to command line prompt. "There are 13 handle(s) keeping the process running." #44

Closed TREVORPINKS closed 1 month ago

TREVORPINKS commented 1 month ago

1.) Copy pasted sample code into a new project. ie.

npm i playwright-with-fingerprints npm i playwright npx playwright install
2.) Ran code from console. 3.) code launched browser, navigated to a page etc, then closed the browser. 4.) After the browser closed, the console application did not return to the command line prompt. It just waited.
5.) I confirmed error doesn't happen when 'playwright-with-fingerprints' excluded from code.
6.) I confirmed error still happens regardless of whether launch or plugin.launchPersistentContext is used. 7.) I added a npm package, 'why-is-node-running' and i got the message that "There are 13 handle(s) keeping the process running." It seems like when browser.close is called the code isn't smart enough to catch this and shut everything down and return.

Is there a work around like calling something just after browser.close()?

PART OF THE OUTPUT

TTYWRAP

(unknown stack trace)

DNSCHANNEL

(unknown stack trace)

PROCESSWRAP

node_modules\bas-remote-node\src\services\engine.js:111 - this._process = execFile( node_modules\bas-remote-node\src\services\engine.js:58 - this._runEngineProcess(port); node_modules\bas-remote-node\src\index.js:142 - await this._engine.start(port); node_modules\browser-with-fingerprints\src\plugin\connector\index.js:41 - await client.start();

PIPEWRAP

node_modules\bas-remote-node\src\services\engine.js:111 - this._process = execFile( node_modules\bas-remote-node\src\services\engine.js:58 - this._runEngineProcess(port);

PIPEWRAP

node_modules\bas-remote-node\src\services\engine.js:111 - this._process = execFile( node_modules\bas-remote-node\src\services\engine.js:58 - this._runEngineProcess(port);

PIPEWRAP

node_modules\bas-remote-node\src\services\engine.js:111 - this._process = execFile( node_modules\bas-remote-node\src\services\engine.js:58 - this._runEngineProcess(port);

TCPWRAP

node_modules\ws\lib\websocket.js:1054 - return net.connect(options); node_modules\ws\lib\websocket.js:868 - req = websocket._req = request(opts); node_modules\ws\lib\websocket.js:88 - initAsClient(this, address, protocols, options); node_modules\bas-remote-node\src\services\socket.js:27 - createWebSocket: (url) => new WebSocket(url),

FILEHANDLE

(unknown stack trace)

FILEHANDLE

(unknown stack trace)

FILEHANDLE

(unknown stack trace)

FILEHANDLE

(unknown stack trace)

SAMPLE CODE: const { plugin } = require('playwright-with-fingerprints');

// Set the service key for the plugin (you can buy it here https://bablosoft.com/directbuy/FingerprintSwitcher/2). // Leave an empty string to use the free version. plugin.setServiceKey('');

(async () => { // Get a fingerprint from the server: const fingerprint = await plugin.fetch({ tags: ['Microsoft Windows', 'Chrome'], });

// Apply fingerprint: plugin.useFingerprint(fingerprint);

// Launch the browser instance: const browser = await plugin.launch();

// The rest of the code is the same as for a standard playwright library: const page = await browser.newPage(); await page.goto('https://example.com');

// Print the browser viewport size: console.log( 'Viewport:', await page.evaluate(() => ({ deviceScaleFactor: window.devicePixelRatio, width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, })) );

await browser.close(); })();

CheshireCaat commented 1 month ago

The internal client for communication with the engine is automatically closed after 5 minutes, until then it keeps the connection.

Unfortunately, there is currently no method that would force it to close, while you can do it manually, for example, patching this file and add an export for the client instance.

Alternatively, a simpler option is to kill the FastExecuteScript process.

TREVORPINKS commented 1 month ago

I don't want to kill a process because that's hacky. and It's a bit off I can't close everything when I want to so I want to fix this.

I am trying to think of a strategy where I would still be able to pull the latest version of the package in the future (without having to re-add my custom changes again).

TREVORPINKS commented 1 month ago

Here is what I did.

Change #1 - browser-with-fingerprints\src\index.d.ts Added a dispose method

export declare class FingerprintPlugin { dispose(): Promise;

Change #2 - browser-with-fingerprints\src\plugin\index.js Added a dispose method

//A. import close from connector const { setup, fetch, versions, setEngineOptions, close } = require('./connector');

//B. added dispose method to FingerprintPlugin class module.exports = class FingerprintPlugin {

async dispose() { console.log('Releasing Resources...') close(); }

Change #3 - browser-with-fingerprints\src\plugin\connector\index.js

Added this code to the bottom of the file:

exports.close = () => dispose();

async function closeClient() { if (client) { await client.close(); } }

async function dispose() { return await lock.acquire('client', async () => { closeClient(); }); }

Now I can exit right away after running. I think you should consider adding a dispose method like this to the code base.

CheshireCaat commented 1 month ago

I made the close method too (CheshireCaat/browser-with-fingerprints@7ae8cfb), but it will be released later when I update the browser version, your option is also suitable.

TREVORPINKS commented 1 month ago

OK Awesome. I'll test it when you release. Just reply to me here.

CheshireCaat commented 1 month ago

The engine has been heavily rewritten, now there is no permanent connection with the engine, therefore the closing method is no longer needed.

TREVORPINKS commented 1 month ago

OK - I tested, the main scenario works where it's someone clicking 'x' to close the browser or where my code closes the browser. But... When I break using CTRL-C the program doesn't exit to the terminal anymore.

Did you consider adding your 'shut down / resource release logic' on a CTRL-C? I basically had a signalListener function that would call the browser.Close() method I added.

  logger.trace('Attaching SIGINT/SIGTERM listeners');
  if (!process.listeners('SIGINT').includes(signalListener)) {
    process.on('SIGINT', signalListener);
  }

  if (!process.listeners('SIGTERM').includes(signalListener)) {
    process.on('SIGTERM', signalListener);
  }
CheshireCaat commented 1 month ago

Can you give an example of code that will not be completed by CTRL-C?

I tested a simple code to get a fingerprint - it exit correctly (used terminal in VSCode).

There is no additional signal processing in the plugins, but now the engine process is terminated automatically upon completion of a particular request.

In other cases, I specifically used unref on timeouts and intervals.

TREVORPINKS commented 1 month ago

OK Thanks for confirming so quickly. I tested this scenario further and i learned that it was just my logger (pino) flushing to the terminal (after pressing ctrl-c and seeing the terminal). So... nothing was hanging... i just had to press enter to see my command prompt again. looks like CTRL-C is covered in your fix just fine. Thanks again.

CheshireCaat commented 1 month ago

Great, glad the problem is solved.