deno: NPM: Playwright does not work
Hi,
I’m trying to use Playwright via the new NPM compatibility layer, but it fails with:
Uncaught TypeError: browserType.launch: Cannot read properties of undefined (reading 'on')
The following snippet can be used to reproduce the error:
import { chromium } from 'npm:playwright';
async function main() {
const browser = await chromium.launch({
headless: false,
});
const page = await browser.newPage();
await page.goto('http://example.com');
await browser.close();
};
if (import.meta.main) {
main()
}
After running the above via deno run --unstable --allow-all main.ts, the following message is displayed:
error: Uncaught Error: browserType.launch: Executable doesn't exist at /home/user/.cache/ms-playwright/chromium-1033/chrome-linux/chrome
╔═════════════════════════════════════════════════════════════════════════╗
║ Looks like Playwright Test or Playwright was just installed or updated. ║
║ Please run the following command to download new browsers: ║
║ ║
║ npx playwright install ║
║ ║
║ <3 Playwright Team ║
╚═════════════════════════════════════════════════════════════════════════╝
Executing deno run --unstable --allow-all npm:playwright install downloads the required binaries.
Executing deno run --unstable --allow-all main.ts again, leads to the above-mentioned error:
error: Uncaught TypeError: browserType.launch: Cannot read properties of undefined (reading 'on')
=========================== logs ===========================
<launching> /home/user/.cache/ms-playwright/chromium-1033/chrome-linux/chrome --disable-field-trial-config --disable-background-networking --enable-features=NetworkService,NetworkServiceInProcess --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-back-forward-cache --disable-breakpad --disable-client-side-phishing-detection --disable-component-extensions-with-background-pages --disable-component-update --no-default-browser-check --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-features=ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate --allow-pre-commit-input --disable-hang-monitor --disable-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --disable-sync --force-color-profile=srgb --metrics-recording-only --no-first-run --enable-automation --password-store=basic --use-mock-keychain --no-service-autorun --export-tagged-pdf --no-sandbox --user-data-dir=/tmp/playwright_chromiumdev_profile-4e2wKK --remote-debugging-pipe --no-startup-window
<launched> pid=835204
============================================================
Playwright/Deno was already discussed in https://github.com/denoland/deno/issues/16298, but I thought it makes sense to open an issue that focuses only on Playwright. There was also some discussion in the Playwright repo: https://github.com/microsoft/playwright/issues/3146
About this issue
- Original URL
- State: open
- Created 2 years ago
- Reactions: 23
- Comments: 23 (6 by maintainers)
No fundamental issues, we are actually eye-balling implementing these APIs in the nearest future and they are required for several other packages too (eg. Next.js).
The code in @d4h0 's code actually can partially work today when the option
useWebSocket: trueis passed tochromium.launchServer. This option doesn’t appear documented, but it almost gets things working out of the box.The problem is that, when the browser is navigated, such as in
await page.goto('http://example.com');, the page will almost immediately close and the following error presents itself in the console:This is effectively covering up the actual error which is somehow leading to the page getting closed by Playwright:
Unfortunately, I didn’t copy the whole stack trace to my notes, and I lost track of where it originally occurred. Might have been from the “Connection” class. Not sure now.
In any case, I’m pretty sure that this seems indicative that there’s something either incorrect with Deno’s WebSocket implementation or with Playwright’s handling of its Node shim for WebSocket. Though it’s possible to successfully use some methods like
evaluateon the initial blank page, any navigation causes this issue.In the meantime, I came up with a way to automate the
connectOverCDPapproach that seems to get Playwright to work with Chromium under Deno with seemingly all basic functionality working properly.It seems we are getting closer, with Deno 1.40.0 …
now downloads the browsers, but the process seems to just hang after fetching them all!
Minor detail, Deno dumps out a deprecation warning about it’s own node compat layer!:
Today I continued to debug “Playwright via Websockets”.
(Btw., I’m describing what I do in so much detail partly so I myself can reproduce it, e.g., if I have to stop and come back a few weeks/months later).
Node.js also has a
--inspect-brkoption. The above exceptions are also raised with Node.js, so don’t seem to be a problem.While debugging, it makes sense to disable the timeout of the
chromium.connectOverCDPcall. The endpoint can also be simplified:(Defining the endpoint as the address of the HTTP server breaks support for the “man-in-the-middle” proxy. If the proxy is needed, then the Websocket address needs to be supplied directly to Playwright)
Importing Playwright via
import { chromium } from 'npm:playwright@^1.29.1';and adding the--reloadoption to Deno updates Playwright to the last version (maybe--reloadwould have been enough).However, that doesn’t seem to change anything
Request & response of the upgraded version
Here is where the exception is triggered (playwright-core/src/server/transport.ts#L82):
The value of the error that this code triggers is:
"ws://127.0.0.1:8080/devtools/browser/dc49d18d-00b4-4a28-9de9-92208e57363d 101 Switching Protocols"transport._wsis an instance ofWebSocketdefined in the ws package.Here are the API docs for the
unexpected-responseevent (not really anything useful).The
requestargument is https://nodejs.org/api/http.html#class-httpclientrequest, and theresponseargument is https://nodejs.org/api/http.html#class-httpincomingmessage.Here is the location where the
unexpected-responseevent is emitted.This code is within an event handler that handles responses for the WebSocket client (which are expected to be redirects).
The odd thing is, that the response has the status code
101 Switching Protocols(see error above, or via debugger).This is odd, because below the response handler is a
upgradeevent handler (see here).It seems, the response is somehow wrongly categories as regular response, instead of as an upgrade…
So I guess, the ‘NPM compat’ code does not implement https://nodejs.org/api/http.html#event-upgrade correctly.
This can be reproduced via the example of Event: ‘upgrade’ (replace the import at the top with
import * as http from "https://deno.land/std@0.170.0/node/http.ts";, to make it compatible with Deno).This example is definitely a much better target to debug, than Playwright directly.
I tried to figure out why the
upgradeevent isn’t emitted, but I’m not familiar with the code base of Deno/Node.js, and I’m not really a TypeScript programmer. It seems, that theupgradeevent isn’t implemented at all for the HTTP client (it is, however, for the HTTP server).I think it makes sense to open a separate issue for this (to eliminate all the unnecessary information). It should be linked below this comment.
It seems deno’s
child_processshim (and ultimatelyDeno.Command) does not yet support to open fds other thanstd{in,out,err}.https://github.com/denoland/deno_std/blob/0.167.0/node/internal/child_process.ts#L153
I meant making a local copy of the playwright lib and change the source code at your will (basically a fork). It’s not great as you imagine. Maybe monkey-patching could work too, I gave up using deno with playwright for the moment…
For your information, puppeteer now works fine if you substitute the “ws” node package with the native WebSocket from deno (https://github.com/denoland/deno/issues/20179). Maybe playwright is similar I didn’t try yet. Unfortunately I have no idea what’s the procedure to fix this properly without resorting to vendoring+patching.
Deno 1.38.2 now implements
process.geteuid()so no longer get the above, but the following…seems ChildProcess is missing
send(). (https://github.com/denoland/deno/issues/12879)But also, after installing browsers via node and manually caching a package, then attempting to run tests…
it did attempt to run the entire test suite, but with lots of warnings…
and then successfully started the test report server and opened the report in my browser.
So, I wonder if it’s actually quite close to working once
messageanddisconnectevents are supported. Do we know if there a fundamental issue preventing those, or simply a case of not got round to it yet? (EDIT: is this related to https://github.com/denoland/deno/issues/16298 ?)Just trying playwright out myself, in Deno 1.38.0, when running:
I get the follow error:
process.geteuid is not a function, full error:Running on MacOS btw, so this won’t affect Windows, but will for Linux users too, looks like this is the cause…
https://github.com/microsoft/playwright/blob/ffd2e02aa3ba2cbaee8e9de01540b6ab66f1ce3b/packages/playwright/src/transform/compilationCache.ts#L49
That sounds fantastic!
Unfortunately, I didn’t have any time yet, to play with Playwright on Deno after the recent changes related to this issue.
It should be possible to run Playwright similarly to how you’d run Playwright on Node.js.
For example, BrowserType::launchServer should be usable to start the browser and to get a WebSocket address via BrowserServer::wsEndpoint.
After that, we should be able to use BrowserType::connect to connect to the WebSocket endpoint (instead of using BrowserType::connectOverCDP, as the last test script does).
For example:
(Save to
main.jsand run viadeno run --unstable --allow-all main.js.--unstablemight not be required anymore, I’m not sure).Unfortunate, that fails with the following error:
Full error
The
pipein the error message made me think, that the issue might be the same as with the default way to run Playwright (Playwright on Node.js normally connects to the browser via pipes, not via a WebSocket), so I tried to disable this “pipe mode” via:…but this doesn’t seem to change anything, unfortunately.
The error is triggered by the first line (that contains
chromium.launchServer) – so a workaround might be:Both options are not great, however, so a fix for the above error would be the ideal solution.
Unfortunately, I don’t have more time to play with this, right now.
@tamusjroyce: Thanks for the tip, this looks pretty interesting.
The biggest advantage of Deno over Node.js (for me) is the fact that you can embed Deno easily into Rust applications (via the deno_core Rust library).
Currently, I’m using Node.js via a homegrown RPC utility. Deno would allow me to integrate much more ergonomically with my applications (also because Deno natively runs Wasm, so I even can run Rust directly in Deno, which makes many things easier).
My main goal for Playwright on Deno is to somehow make it possible to ergonomically use Playwright from Rust (there is already playwright-rust, but it doesn’t support the latest Playwright version, and embeds a Zip binary of a Node.js application, which is unzipped at runtime, which I don’t really like).
That being said, I probably will still start using
kt3k/deno-binwith my current RPC-setup to get used to and benefit from the better tooling that Deno offers. Thanks again!@bartlomieju: Thank you for the feedback!
Is there also a rewrite of the HTTP client planned?
In theory, it shouldn’t be too difficult to add HTTP upgrade support to the client. In practice, I gave up because I’m too unfamiliar with the code base and TypeScript itself (also, because I already spent three full days debugging this).
I think, Deno is in almost every way better than Node.js. Unfortunately, Playwright is essential for me, and it is too much work to maintain systems written for two different JavaScript runtimes. So, at least for me, the lack of Playwright on Deno is blocker for switching to Deno (besides Playwright, everything I need can be replaced with something else that works on Deno).
Hey @d4h0 thanks for detailed examination, this should be enough to debug the problem on our side.
I can answer some questions outright:
That’s probably because you used unversioned import and you had older version of Playwright cached, as you noticed
--reloadflag fixed the problem (ie. Deno pulls the latest Playwrigth package available). Thenode/16.17is because that’s what we were targeting in our Node compat layer, but it should most likely be updated as we target latest LTS version these days - opened https://github.com/denoland/deno_std/issues/3057 to fix it.This seems like main crux of the problem and we received reports that Vite is not working properly with Deno too; it uses HTTP upgrade as well. We are currently working on a rewrite of our HTTP server that should help us fix this problem. Obviously if we could fix up the problem before rewrite of HTTP server lands that would be preferable. @kt3k could you take a look at this problem?
EDIT: Ooops, it seems the problem is with the HTTP client, not HTTP server. So the Vite problem is not related to this one.
http://docs.libuv.org/en/v1.x/guide/processes.html
This is correct. Deno doesn’t currently support libuv’s cross platform api for fds other than std{in,out,err}. via https://github.com/denoland/deno/issues/16298
libuv’s icon is a dino with a unicorn. Kind of makes you wonder. 😃
But yes. My incident is about supporting all of libuv. Not just a few additional pipelines beyond stdin/stdout/stderr.
I ran
deno run --unstable --allow-all --inspect-brk main.tsand examined the call stack.Screenshot
The cause is that
stdio[3]andstdio[4]are undefined here. Through these pipes Playwright communicates with the launched Chromium process.https://github.com/microsoft/playwright/blob/v1.28.1/packages/playwright-core/src/server/browserType.ts#L257-L258