diff --git a/package-lock.json b/package-lock.json index eb90783..91495a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -299,431 +299,6 @@ "node": ">=6.9.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", - "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", - "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", - "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", - "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", - "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", - "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", - "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", - "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", - "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", - "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", - "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", - "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", - "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", - "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", - "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", - "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", - "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", - "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", - "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", - "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", - "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", - "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", - "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", - "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", - "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@hono/node-server": { "version": "1.19.9", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", @@ -827,10 +402,6 @@ } } }, - "node_modules/@playwright/cli": { - "resolved": "packages/playwright-cli", - "link": true - }, "node_modules/@playwright/mcp": { "resolved": "packages/playwright-mcp", "link": true @@ -1268,16 +839,6 @@ "@types/har-format": "*" } }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1309,20 +870,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "24.3.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz", @@ -1813,47 +1360,6 @@ "node": ">= 0.4" } }, - "node_modules/esbuild": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", - "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.2", - "@esbuild/android-arm": "0.24.2", - "@esbuild/android-arm64": "0.24.2", - "@esbuild/android-x64": "0.24.2", - "@esbuild/darwin-arm64": "0.24.2", - "@esbuild/darwin-x64": "0.24.2", - "@esbuild/freebsd-arm64": "0.24.2", - "@esbuild/freebsd-x64": "0.24.2", - "@esbuild/linux-arm": "0.24.2", - "@esbuild/linux-arm64": "0.24.2", - "@esbuild/linux-ia32": "0.24.2", - "@esbuild/linux-loong64": "0.24.2", - "@esbuild/linux-mips64el": "0.24.2", - "@esbuild/linux-ppc64": "0.24.2", - "@esbuild/linux-riscv64": "0.24.2", - "@esbuild/linux-s390x": "0.24.2", - "@esbuild/linux-x64": "0.24.2", - "@esbuild/netbsd-arm64": "0.24.2", - "@esbuild/netbsd-x64": "0.24.2", - "@esbuild/openbsd-arm64": "0.24.2", - "@esbuild/openbsd-x64": "0.24.2", - "@esbuild/sunos-x64": "0.24.2", - "@esbuild/win32-arm64": "0.24.2", - "@esbuild/win32-ia32": "0.24.2", - "@esbuild/win32-x64": "0.24.2" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2435,7 +1941,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3835,6 +3340,7 @@ "packages/playwright-cli": { "name": "@playwright/cli", "version": "0.0.56", + "extraneous": true, "license": "Apache-2.0", "devDependencies": { "@types/debug": "^4.1.12", @@ -3853,11 +3359,13 @@ "version": "0.0.56", "license": "Apache-2.0", "dependencies": { + "minimist": "^1.2.5", "playwright": "1.59.0-alpha-1769176698000", "playwright-core": "1.59.0-alpha-1769176698000" }, "bin": { - "mcp-server-playwright": "cli.js" + "mcp-server-playwright": "cli.js", + "playwright-cli": "playwright-cli.js" }, "engines": { "node": ">=18" diff --git a/packages/playwright-cli/.gitignore b/packages/playwright-cli/.gitignore deleted file mode 100644 index f1ff06d..0000000 --- a/packages/playwright-cli/.gitignore +++ /dev/null @@ -1 +0,0 @@ -lib/ \ No newline at end of file diff --git a/packages/playwright-cli/package.json b/packages/playwright-cli/package.json deleted file mode 100644 index 0964937..0000000 --- a/packages/playwright-cli/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "@playwright/cli", - "version": "0.0.56", - "description": "Playwright CLI", - "private": true, - "repository": { - "type": "git", - "url": "git+https://github.com/microsoft/playwright-mcp.git" - }, - "homepage": "https://playwright.dev", - "engines": { - "node": ">=18" - }, - "author": { - "name": "Microsoft Corporation" - }, - "license": "Apache-2.0", - "scripts": { - "lint": "tsc --project .", - "build": "esbuild src/cli.ts --bundle --platform=node --target=node18 --format=cjs --outfile=lib/cli.js", - "watch": "esbuild src/cli.ts --bundle --platform=node --target=node18 --format=cjs --outfile=lib/cli.js --watch", - "clean": "rm -rf lib" - }, - "devDependencies": { - "@types/debug": "^4.1.12", - "@types/minimist": "^1.2.5", - "debug": "^4.4.3", - "esbuild": "^0.24.0", - "minimist": "^1.2.8", - "typescript": "^5.8.2" - } -} diff --git a/packages/playwright-cli/src/cli.ts b/packages/playwright-cli/src/cli.ts deleted file mode 100644 index 094adf0..0000000 --- a/packages/playwright-cli/src/cli.ts +++ /dev/null @@ -1,331 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable no-console */ -/* eslint-disable no-restricted-properties */ - -import { spawn } from 'child_process'; -import crypto from 'crypto'; -import fs from 'fs'; -import net from 'net'; -import os from 'os'; -import path from 'path'; -import { debug } from 'debug'; -import minimist from 'minimist'; -import { SocketConnection } from './socketConnection'; - -const debugCli = debug('pw:cli'); - -const packageJSON = require('../package.json'); - -async function runCliCommand(sessionName: string, args: any) { - const session = await connectToDaemon(sessionName); - const result = await session.runCliCommand(args); - console.log(result); - session.dispose(); -} - -async function socketExists(socketPath: string): Promise { - try { - const stat = await fs.promises.stat(socketPath); - if (stat?.isSocket()) - return true; - } catch (e) { - } - return false; -} - -class SocketSession { - private _connection: SocketConnection; - private _nextMessageId = 1; - private _callbacks = new Map void, reject: (e: Error) => void }>(); - - constructor(connection: SocketConnection) { - this._connection = connection; - this._connection.onmessage = message => this._onMessage(message); - this._connection.onclose = () => this.dispose(); - } - - async runCliCommand(args: any): Promise { - return await this._send('runCliCommand', { args }); - } - - private async _send(method: string, params: any = {}): Promise { - const messageId = this._nextMessageId++; - const message = { - id: messageId, - method, - params, - }; - await this._connection.send(message); - return new Promise((resolve, reject) => { - this._callbacks.set(messageId, { resolve, reject }); - }); - } - - dispose() { - for (const callback of this._callbacks.values()) - callback.reject(new Error('Disposed')); - this._callbacks.clear(); - this._connection.close(); - } - - private _onMessage(object: any) { - if (object.id && this._callbacks.has(object.id)) { - const callback = this._callbacks.get(object.id)!; - this._callbacks.delete(object.id); - if (object.error) - callback.reject(new Error(object.error)); - else - callback.resolve(object.result); - } else if (object.id) { - throw new Error(`Unexpected message id: ${object.id}`); - } else { - throw new Error(`Unexpected message without id: ${JSON.stringify(object)}`); - } - } -} - -function localCacheDir(): string { - if (process.platform === 'linux') - return process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache'); - if (process.platform === 'darwin') - return path.join(os.homedir(), 'Library', 'Caches'); - if (process.platform === 'win32') - return process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'); - throw new Error('Unsupported platform: ' + process.platform); -} - -function playwrightCacheDir(): string { - return path.join(localCacheDir(), 'ms-playwright'); -} - -function calculateSha1(buffer: Buffer | string): string { - const hash = crypto.createHash('sha1'); - hash.update(buffer); - return hash.digest('hex'); -} - -function socketDirHash(): string { - return calculateSha1(__dirname); -} - -function daemonSocketDir(): string { - return path.resolve(playwrightCacheDir(), 'daemon', socketDirHash()); -} - -function daemonSocketPath(sessionName: string): string { - const socketName = `${sessionName}.sock`; - if (os.platform() === 'win32') - return `\\\\.\\pipe\\${socketDirHash()}-${socketName}`; - return path.resolve(daemonSocketDir(), socketName); -} - -async function connectToDaemon(sessionName: string): Promise { - const socketPath = daemonSocketPath(sessionName); - debugCli(`Connecting to daemon at ${socketPath}`); - - if (await socketExists(socketPath)) { - debugCli(`Socket file exists, attempting to connect...`); - try { - return await connectToSocket(socketPath); - } catch (e) { - // Connection failed, delete the stale socket file. - if (os.platform() !== 'win32') - await fs.promises.unlink(socketPath).catch(() => {}); - } - } - - debugCli(`Will launch daemon process: npx playwright run-mcp-server`); - const userDataDir = path.resolve(daemonSocketDir(), `${sessionName}-user-data`); - const child = spawn('npx', ['playwright', 'run-mcp-server', `--daemon=${socketPath}`, `--user-data-dir=${userDataDir}`], { - detached: true, - stdio: 'ignore', - cwd: process.cwd(), // Will be used as root. - }); - child.unref(); - - // Wait for the socket to become available with retries. - const maxRetries = 50; - const retryDelay = 100; // ms - for (let i = 0; i < maxRetries; i++) { - await new Promise(resolve => setTimeout(resolve, 100)); - try { - return await connectToSocket(socketPath); - } catch (e: any) { - if (e.code !== 'ENOENT') - throw e; - debugCli(`Retrying to connect to daemon at ${socketPath} (${i + 1}/${maxRetries})`); - } - } - throw new Error(`Failed to connect to daemon at ${socketPath} after ${maxRetries * retryDelay}ms`); -} - -async function connectToSocket(socketPath: string): Promise { - const socket = await new Promise((resolve, reject) => { - const socket = net.createConnection(socketPath, () => { - debugCli(`Connected to daemon at ${socketPath}`); - resolve(socket); - }); - socket.on('error', reject); - }); - return new SocketSession(new SocketConnection(socket)); -} - -function currentSessionPath(): string { - return path.resolve(daemonSocketDir(), 'current-session'); -} - -async function getCurrentSession(): Promise { - try { - const session = await fs.promises.readFile(currentSessionPath(), 'utf-8'); - return session.trim() || 'default'; - } catch { - return 'default'; - } -} - -async function setCurrentSession(sessionName: string): Promise { - await fs.promises.mkdir(daemonSocketDir(), { recursive: true }); - await fs.promises.writeFile(currentSessionPath(), sessionName); -} - -async function canConnectToSocket(socketPath: string): Promise { - return new Promise(resolve => { - const socket = net.createConnection(socketPath, () => { - socket.destroy(); - resolve(true); - }); - socket.on('error', () => { - resolve(false); - }); - }); -} - -async function listSessions(): Promise<{ name: string, live: boolean }[]> { - const dir = daemonSocketDir(); - try { - const files = await fs.promises.readdir(dir); - const sessions: { name: string, live: boolean }[] = []; - for (const file of files) { - if (file.endsWith('-user-data')) { - const sessionName = file.slice(0, -'-user-data'.length); - const socketPath = daemonSocketPath(sessionName); - const live = await canConnectToSocket(socketPath); - sessions.push({ name: sessionName, live }); - } - } - return sessions; - } catch { - return []; - } -} - -function resolveSessionName(args: any): string { - if (args.session) - return args.session; - if (process.env.PLAYWRIGHT_CLI_SESSION) - return process.env.PLAYWRIGHT_CLI_SESSION; - return 'default'; -} - -async function handleSessionCommand(args: any): Promise { - const subcommand = args._[1]; - - if (!subcommand) { - // Show current session - const current = await getCurrentSession(); - console.log(current); - return; - } - - if (subcommand === 'list') { - const sessions = await listSessions(); - const current = await getCurrentSession(); - console.log('Sessions:'); - for (const session of sessions) { - const marker = session.name === current ? '->' : ' '; - const liveMarker = session.live ? ' (live)' : ''; - console.log(`${marker} ${session.name}${liveMarker}`); - } - if (sessions.length === 0) - console.log(' (no sessions)'); - return; - } - - if (subcommand === 'set') { - const sessionName = args._[2]; - if (!sessionName) { - console.error('Usage: playwright-cli session set '); - process.exit(1); - } - await setCurrentSession(sessionName); - console.log(`Current session set to: ${sessionName}`); - return; - } - - console.error(`Unknown session subcommand: ${subcommand}`); - process.exit(1); -} - -async function main() { - const argv = process.argv.slice(2); - const args = minimist(argv); - const commandName = args._[0]; - - if (args.version || args.v) { - console.log(packageJSON.version); - process.exit(0); - } - - // Handle 'session' command specially - it doesn't need daemon connection - if (commandName === 'session') { - await handleSessionCommand(args); - return; - } - - const help = require('./help.json'); - const command = help.commands[commandName]; - if (args.help || args.h) { - if (command) { - console.log(command); - } else { - console.log('playwright-cli - run playwright mcp commands from terminal\n'); - console.log(help.global); - } - process.exit(0); - } - if (!command) { - console.error(`Unknown command: ${commandName}\n`); - console.log(help.global); - process.exit(1); - } - - // Resolve session name: --session flag > PLAYWRIGHT_CLI_SESSION env > current session > 'default' - let sessionName = resolveSessionName(args); - if (sessionName === 'default' && !args.session && !process.env.PLAYWRIGHT_CLI_SESSION) - sessionName = await getCurrentSession(); - - runCliCommand(sessionName, args).catch(e => { - console.error(e.message); - process.exit(1); - }); -} - -main().catch(e => { - console.error(e.message); - process.exit(1); -}); diff --git a/packages/playwright-cli/src/help.json b/packages/playwright-cli/src/help.json deleted file mode 100644 index e064df9..0000000 --- a/packages/playwright-cli/src/help.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "global": "Usage: playwright-cli [options]\nCommands:\n click perform click on a web page\n close close the page\n dblclick perform double click on a web page\n console returns all console messages\n drag perform drag and drop between two elements\n evaluate evaluate javascript expression on page or element\n upload-file upload one or multiple files\n handle-dialog handle a dialog\n hover hover over element on page\n open open url\n go-back go back to the previous page\n network-requests returns all network requests since loading the page\n press-key press a key on the keyboard\n resize resize the browser window\n run-code run playwright code snippet\n select-option select an option in a dropdown\n snapshot capture accessibility snapshot of the current page, this is better than screenshot\n screenshot take a screenshot of the current page. you can't perform actions based on the screenshot, use browser_snapshot for actions.\n type type text into editable element\n wait-for wait for text to appear or disappear or a specified time to pass\n tab close a browser tab\n mouse-click-xy click left mouse button at a given position\n mouse-drag-xy drag left mouse button to a given position\n mouse-move-xy move mouse to a given position\n pdf-save save page as pdf\n start-tracing start trace recording\n stop-tracing stop trace recording", - "commands": { - "click": "playwright-cli click \n\nPerform click on a web page\n\nArguments:\n \tExact target element reference from the page snapshot\nOptions:\n --button\tbutton to click, defaults to left\n --modifiers\tmodifier keys to press", - "close": "playwright-cli close \n\nClose the page\n", - "dblclick": "playwright-cli dblclick \n\nPerform double click on a web page\n\nArguments:\n \tExact target element reference from the page snapshot\nOptions:\n --button\tbutton to click, defaults to left\n --modifiers\tmodifier keys to press", - "console": "playwright-cli console \n\nReturns all console messages\n\nArguments:\n \tLevel of the console messages to return. Each level includes the messages of more severe levels. Defaults to \"info\".", - "drag": "playwright-cli drag \n\nPerform drag and drop between two elements\n\nArguments:\n \tExact source element reference from the page snapshot\n \tExact target element reference from the page snapshot\nOptions:\n --headed\trun browser in headed mode", - "evaluate": "playwright-cli evaluate \n\nEvaluate JavaScript expression on page or element\n\nArguments:\n \t() => { /* code */ } or (element) => { /* code */ } when element is provided\n \tExact target element reference from the page snapshot", - "upload-file": "playwright-cli upload-file \n\nUpload one or multiple files\n\nOptions:\n --paths\tthe absolute paths to the files to upload. can be single file or multiple files. if omitted, file chooser is cancelled.", - "handle-dialog": "playwright-cli handle-dialog \n\nHandle a dialog\n\nArguments:\n \tWhether to accept the dialog.\n \tThe text of the prompt in case of a prompt dialog.", - "hover": "playwright-cli hover \n\nHover over element on page\n\nArguments:\n \tExact target element reference from the page snapshot", - "open": "playwright-cli open \n\nOpen URL\n\nArguments:\n \tThe URL to navigate to\nOptions:\n --headed\trun browser in headed mode", - "go-back": "playwright-cli go-back \n\nGo back to the previous page\n", - "network-requests": "playwright-cli network-requests \n\nReturns all network requests since loading the page\n\nOptions:\n --includeStatic\twhether to include successful static resources like images, fonts, scripts, etc. defaults to false.", - "press-key": "playwright-cli press-key \n\nPress a key on the keyboard\n\nArguments:\n \tName of the key to press or a character to generate, such as `ArrowLeft` or `a`", - "resize": "playwright-cli resize \n\nResize the browser window\n\nArguments:\n \tWidth of the browser window\n \tHeight of the browser window", - "run-code": "playwright-cli run-code \n\nRun Playwright code snippet\n\nArguments:\n \tA JavaScript function containing Playwright code to execute. It will be invoked with a single argument, page, which you can use for any page interaction.", - "select-option": "playwright-cli select-option \n\nSelect an option in a dropdown\n\nArguments:\n \tExact target element reference from the page snapshot\n \tArray of values to select in the dropdown. This can be a single value or multiple values.", - "snapshot": "playwright-cli snapshot \n\nCapture accessibility snapshot of the current page, this is better than screenshot\n\nOptions:\n --filename\tsave snapshot to markdown file instead of returning it in the response.", - "screenshot": "playwright-cli screenshot \n\nTake a screenshot of the current page. You can't perform actions based on the screenshot, use browser_snapshot for actions.\n\nArguments:\n \tExact target element reference from the page snapshot.\nOptions:\n --filename\tfile name to save the screenshot to. defaults to `page-{timestamp}.{png|jpeg}` if not specified.\n --fullPage\twhen true, takes a screenshot of the full scrollable page, instead of the currently visible viewport.", - "type": "playwright-cli type \n\nType text into editable element\n\nArguments:\n \tExact target element reference from the page snapshot\n \tText to type into the element\nOptions:\n --submit\twhether to submit entered text (press enter after)\n --slowly\twhether to type one character at a time. useful for triggering key handlers in the page.", - "wait-for": "playwright-cli wait-for \n\nWait for text to appear or disappear or a specified time to pass\n\nOptions:\n --time\tthe time to wait in seconds\n --text\tthe text to wait for\n --textGone\tthe text to wait for to disappear", - "tab": "playwright-cli tab \n\nClose a browser tab\n\nArguments:\n \tAction to perform on tabs, 'list' | 'new' | 'close' | 'select'\n \tTab index. If omitted, current tab is closed.", - "mouse-click-xy": "playwright-cli mouse-click-xy \n\nClick left mouse button at a given position\n\nArguments:\n \tX coordinate\n \tY coordinate", - "mouse-drag-xy": "playwright-cli mouse-drag-xy \n\nDrag left mouse button to a given position\n\nArguments:\n \tStart X coordinate\n \tStart Y coordinate\n \tEnd X coordinate\n \tEnd Y coordinate", - "mouse-move-xy": "playwright-cli mouse-move-xy \n\nMove mouse to a given position\n\nArguments:\n \tX coordinate\n \tY coordinate", - "pdf-save": "playwright-cli pdf-save \n\nSave page as PDF\n\nOptions:\n --filename\tfile name to save the pdf to. defaults to `page-{timestamp}.pdf` if not specified.", - "start-tracing": "playwright-cli start-tracing \n\nStart trace recording\n", - "stop-tracing": "playwright-cli stop-tracing \n\nStop trace recording\n" - } -} \ No newline at end of file diff --git a/packages/playwright-cli/src/socketConnection.ts b/packages/playwright-cli/src/socketConnection.ts deleted file mode 100644 index 8de8f81..0000000 --- a/packages/playwright-cli/src/socketConnection.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import net from 'net'; - -import { debug } from 'debug'; - -const daemonDebug = debug('pw:daemon'); - -export class SocketConnection { - private _socket: net.Socket; - private _pendingBuffers: Buffer[] = []; - - onclose?: () => void; - onmessage?: (message: any) => void; - - constructor(socket: net.Socket) { - this._socket = socket; - socket.on('data', buffer => this._onData(buffer)); - socket.on('close', () => { - this.onclose?.(); - }); - socket.on('error', e => daemonDebug(`error: ${e.message}`)); - } - - async send(message: any) { - await new Promise((resolve, reject) => { - this._socket.write(`${JSON.stringify(message)}\n`, error => { - if (error) - reject(error); - else - resolve(undefined); - }); - }); - } - - close() { - this._socket.destroy(); - } - - private _onData(buffer: Buffer) { - let end = buffer.indexOf('\n'); - if (end === -1) { - this._pendingBuffers.push(buffer); - return; - } - this._pendingBuffers.push(buffer.slice(0, end)); - const message = Buffer.concat(this._pendingBuffers).toString(); - this._dispatchMessage(message); - - let start = end + 1; - end = buffer.indexOf('\n', start); - while (end !== -1) { - const message = buffer.toString(undefined, start, end); - this._dispatchMessage(message); - start = end + 1; - end = buffer.indexOf('\n', start); - } - this._pendingBuffers = [buffer.slice(start)]; - } - - private _dispatchMessage(message: string) { - try { - this.onmessage?.(JSON.parse(message)); - } catch (e) { - daemonDebug('failed to dispatch message', e); - } - } -} diff --git a/packages/playwright-cli/tsconfig.json b/packages/playwright-cli/tsconfig.json deleted file mode 100644 index 02aeb89..0000000 --- a/packages/playwright-cli/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "esModuleInterop": true, - "moduleResolution": "node", - "strict": true, - "module": "ESNext", - "rootDir": "src", - "outDir": "./lib", - "resolveJsonModule": true, - "types": ["node"], - "noEmit": true - }, - "include": [ - "src", - ] -} diff --git a/packages/playwright-mcp/.npmignore b/packages/playwright-mcp/.npmignore index 28e12e4..1b481d2 100644 --- a/packages/playwright-mcp/.npmignore +++ b/packages/playwright-mcp/.npmignore @@ -2,5 +2,6 @@ !README.md !LICENSE !cli.js +!playwright-cli.js !index.* !config.d.ts diff --git a/packages/playwright-mcp/package.json b/packages/playwright-mcp/package.json index 5a42c8f..cbbbb93 100644 --- a/packages/playwright-mcp/package.json +++ b/packages/playwright-mcp/package.json @@ -15,14 +15,13 @@ }, "license": "Apache-2.0", "scripts": { - "lint": "npm run update-readme", - "update-readme": "node update-readme.js", + "lint": "node update-readme.js", "test": "playwright test", "ctest": "playwright test --project=chrome", "ftest": "playwright test --project=firefox", "wtest": "playwright test --project=webkit", "dtest": "MCP_IN_DOCKER=1 playwright test --project=chromium-docker", - "npm-publish": "npm run clean && npm run lint && npm run test && npm publish", + "npm-publish": "npm run lint && npm run test && npm publish", "copy-config": "cp ../../../playwright/packages/playwright/src/mcp/config.d.ts . && perl -pi -e \"s|import type \\* as playwright from 'playwright-core';|import type * as playwright from 'playwright';|\" ./config.d.ts", "roll": "npm run copy-config && npm run lint" }, @@ -34,10 +33,12 @@ } }, "dependencies": { + "minimist": "^1.2.5", "playwright": "1.59.0-alpha-1769176698000", "playwright-core": "1.59.0-alpha-1769176698000" }, "bin": { - "mcp-server-playwright": "cli.js" + "mcp": "cli.js", + "playwright-cli": "./playwright-cli.js" } } diff --git a/packages/playwright-mcp/playwright-cli.js b/packages/playwright-mcp/playwright-cli.js new file mode 100755 index 0000000..1143597 --- /dev/null +++ b/packages/playwright-mcp/playwright-cli.js @@ -0,0 +1,23 @@ +#!/usr/bin/env node +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { program } = require('playwright/lib/mcp/terminal/program'); +const packageJSON = require('./package.json'); +program({ version: packageJSON.version }).catch(e => { + console.error(e.message); + process.exit(1); +}); diff --git a/packages/playwright-mcp/tests/cli.spec.ts b/packages/playwright-mcp/tests/cli.spec.ts new file mode 100644 index 0000000..465fad1 --- /dev/null +++ b/packages/playwright-mcp/tests/cli.spec.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { test, expect } from '@playwright/test'; +import childProcess from 'child_process'; + +function runCLI(args: string[]) { + return childProcess.spawnSync(process.execPath, [require.resolve('../playwright-cli'), ...args], { encoding: 'utf-8' }); +} + +test('prints help', async () => { + const { stdout } = runCLI(['--help']); + expect(stdout).toContain('Usage: playwright-cli '); +}); + +test('prints version', async () => { + const { stdout } = runCLI(['--version']); + expect(stdout).toContain(require('../package.json').version); +});