diff --git a/package-lock.json b/package-lock.json index 26db054a6d..36c9f186de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22169,6 +22169,7 @@ "version": "1.58.2", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "dev": true, "license": "Apache-2.0", "dependencies": { "playwright-core": "1.58.2" @@ -22187,6 +22188,7 @@ "version": "1.58.2", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "dev": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -22199,6 +22201,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -25894,6 +25897,536 @@ "version": "2.8.1", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -28042,14 +28575,12 @@ "tools/benchmark-site-editor": { "version": "0.0.1", "license": "GPLv2", - "dependencies": { - "@wp-playground/cli": "3.1.2", - "chalk": "^5.3.0", - "playwright": "^1.58.1", - "ts-node": "^10.9.2" - }, "devDependencies": { "@types/node": "^25.3.2", + "@wp-playground/cli": "^3.0.22", + "chalk": "^5.3.0", + "playwright": "^1.58.1", + "tsx": "^4.7.0", "typescript": "^5.0.0" } }, @@ -28067,6 +28598,7 @@ "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" diff --git a/tools/benchmark-site-editor/.gitignore b/tools/benchmark-site-editor/.gitignore new file mode 100644 index 0000000000..2c4d248c80 --- /dev/null +++ b/tools/benchmark-site-editor/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +package-lock.json +dist/ +results-*.json diff --git a/tools/benchmark-site-editor/README.md b/tools/benchmark-site-editor/README.md index ab4cbcbb39..ea235c94e9 100644 --- a/tools/benchmark-site-editor/README.md +++ b/tools/benchmark-site-editor/README.md @@ -1,6 +1,6 @@ # Site Editor Performance Benchmark -Benchmarks site editor performance across Studio, Playground CLI, and Playground Web environments, with optional plugin and multi-worker configurations. +Benchmarks site editor performance across Studio, Playground CLI, Playground Web, and custom WordPress environments, with optional plugin and multi-worker configurations. ## Related Issue @@ -32,6 +32,7 @@ The benchmark launches a headless Chromium browser against each environment, mea | Playground CLI + MW + Plugins | `pg-cli-mw-plugins` | Playground CLI with multi-worker and 10 plugins | | Playground Web | `pg-web` | playground.wordpress.net (bare) | | Playground Web + Plugins | `pg-web-plugins` | playground.wordpress.net with 10 plugins | +| Custom | user-defined | Any running WordPress site via `--custom` | ## Plugins @@ -46,12 +47,12 @@ When the "plugins" variant is enabled, these 10 plugins are installed via a blue - Jetpack VideoPress - WooCommerce Payments - Contact Form 7 -- Elementor +- CoBlocks ## Usage ```bash -cd scripts/benchmark-site-editor +cd tools/benchmark-site-editor npm install npm run benchmark ``` @@ -59,12 +60,17 @@ npm run benchmark ### Options ``` ---rounds=N Number of benchmark runs per environment (default: 1) ---skip-studio Skip Studio environments ---skip-playground-cli Skip Playground CLI environments ---skip-playground-web Skip Playground web environments ---only= Run only named environments (comma-separated) ---help Show help +--rounds=N Number of benchmark runs per environment (default: 1) +--skip-studio Skip Studio environments +--skip-playground-cli Skip Playground CLI environments +--skip-playground-web Skip Playground web environments +--custom=,[,,] Add a custom WordPress site (repeatable) + user defaults to "admin", password to "password" +--install-plugins Install blueprint plugins on ALL custom environments +--install-plugins=, Install blueprint plugins on specific custom environments +--only= Run only named environments (comma-separated) +--headed Launch browser in headed mode for debugging +--help Show help ``` ### Examples @@ -81,8 +87,38 @@ npm run benchmark -- --skip-studio --skip-playground-web # Single specific environment npm run benchmark -- --only=studio-mw-plugins --rounds=5 + +# Benchmark a single custom WordPress site +npm run benchmark -- --custom=my-site,http://localhost:10003 + +# Two custom sites: bare vs with plugins +npm run benchmark -- \ + --custom=local-bare,http://localhost:10003 \ + --custom=local-plugins,http://localhost:10004 \ + --install-plugins=local-plugins + +# Custom site with non-default credentials +npm run benchmark -- --custom=my-site,http://localhost:10003,admin,secret + +# Compare Studio vs a custom site +npm run benchmark -- --only=studio,my-site --custom=my-site,http://localhost:10003 --rounds=3 ``` +## Custom Environments + +Use `--custom` to add any running WordPress site to the benchmark. The flag is repeatable, so you can benchmark multiple custom sites in a single run. Each site must be accessible and have a WordPress admin account. + +Format: `--custom=,[,,]` + +- **name** — Label for the environment in the results table +- **url** — Base URL of the running WordPress site +- **user** — WordPress admin username (default: `admin`) +- **password** — WordPress admin password (default: `password`) + +The benchmark logs in via `wp-login.php` using the provided credentials. + +With `--install-plugins`, the benchmark installs the same set of plugins used by the built-in environments via the WordPress REST API before measuring. Use `--install-plugins` to install on all custom environments, or `--install-plugins=name1,name2` to target specific ones. + ## Prerequisites - **Studio CLI**: Built automatically if `dist/cli/main.js` doesn't exist (`npm run cli:build`) diff --git a/tools/benchmark-site-editor/benchmark.ts b/tools/benchmark-site-editor/benchmark.ts index 7fb11d097f..2ad7dd3d3b 100644 --- a/tools/benchmark-site-editor/benchmark.ts +++ b/tools/benchmark-site-editor/benchmark.ts @@ -7,9 +7,10 @@ * - Studio (bare, multi-worker, plugins, multi-worker+plugins) * - Playground CLI (bare, multi-worker, plugins, multi-worker+plugins) * - Playground Web (bare, plugins) + * - Custom (any running WordPress site via --custom-url) * * Usage: - * cd scripts/benchmark-site-editor + * cd tools/benchmark-site-editor * npm install * npm run benchmark * @@ -18,6 +19,8 @@ * --skip-studio Skip Studio environments * --skip-playground-cli Skip Playground CLI environments * --skip-playground-web Skip Playground web environments + * --custom=,[,,] Add a custom WordPress site (repeatable) + * --install-plugins[=,] Install plugins from blueprint via WP REST API * --only= Run only these environments (comma-separated) * --help Show help */ @@ -27,7 +30,8 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; import chalk from 'chalk'; -import { measureSiteEditor, METRIC_NAMES, type MeasurementResult } from './measure-site-editor.ts'; +import { installPlugins } from './install-plugins.js'; +import { measureSiteEditor, METRIC_NAMES, type MeasurementResult } from './measure-site-editor.js'; // --------------------------------------------------------------------------- // Configuration @@ -54,13 +58,17 @@ const PLAYGROUND_CLI_PORT_BASE = 9500; // Types // --------------------------------------------------------------------------- -type EnvironmentType = 'studio' | 'playground-cli' | 'playground-web'; +type EnvironmentType = 'studio' | 'playground-cli' | 'playground-web' | 'custom'; interface EnvironmentConfig { name: string; type: EnvironmentType; plugins: boolean; multiWorker: boolean; + /** Base URL for custom environments. */ + customUrl?: string; + /** WordPress admin credentials for custom environments. */ + credentials?: { username: string; password: string }; } interface BenchmarkResult { @@ -94,6 +102,13 @@ const ALL_ENVIRONMENTS: EnvironmentConfig[] = [ // Argument parsing // --------------------------------------------------------------------------- +interface CustomEnvInput { + name: string; + url: string; + user: string; + password: string; +} + interface Options { rounds: number; skipStudio: boolean; @@ -101,6 +116,25 @@ interface Options { skipPlaygroundWeb: boolean; only: string[]; headed: boolean; + customs: CustomEnvInput[]; + /** Names of custom environments to install plugins on, or 'all'. */ + installPlugins: string[] | 'all' | false; +} + +function parseCustomFlag( value: string ): CustomEnvInput { + const parts = value.split( ',' ); + if ( parts.length < 2 ) { + console.error( + `Invalid --custom value: "${ value }"\nExpected: --custom=name,url[,user,password]` + ); + process.exit( 1 ); + } + return { + name: parts[ 0 ], + url: parts[ 1 ], + user: parts[ 2 ] || 'admin', + password: parts[ 3 ] || 'password', + }; } function parseArgs(): Options { @@ -112,6 +146,8 @@ function parseArgs(): Options { skipPlaygroundWeb: false, only: [], headed: false, + customs: [], + installPlugins: false, }; for ( const arg of args ) { @@ -123,6 +159,15 @@ function parseArgs(): Options { opts.skipPlaygroundCli = true; } else if ( arg === '--skip-playground-web' ) { opts.skipPlaygroundWeb = true; + } else if ( arg.startsWith( '--custom=' ) ) { + opts.customs.push( parseCustomFlag( arg.split( '=' ).slice( 1 ).join( '=' ) ) ); + } else if ( arg === '--install-plugins' ) { + opts.installPlugins = 'all'; + } else if ( arg.startsWith( '--install-plugins=' ) ) { + opts.installPlugins = arg + .split( '=' )[ 1 ] + .split( ',' ) + .map( ( s ) => s.trim() ); } else if ( arg.startsWith( '--only=' ) ) { opts.only = arg .split( '=' )[ 1 ] @@ -144,15 +189,35 @@ function printHelp() { Usage: npm run benchmark [options] Options: - --rounds=N Number of benchmark runs per environment (default: 1) - --skip-studio Skip Studio environments - --skip-playground-cli Skip Playground CLI environments - --skip-playground-web Skip Playground web environments - --only= Run only named environments (comma-separated) - --headed Launch browser in headed mode for debugging - --help Show this help message - -Environments: ${ ALL_ENVIRONMENTS.map( ( e ) => e.name ).join( ', ' ) } + --rounds=N Number of benchmark runs per environment (default: 1) + --skip-studio Skip Studio environments + --skip-playground-cli Skip Playground CLI environments + --skip-playground-web Skip Playground web environments + --custom=,[,,] Add a custom WordPress site (repeatable) + user defaults to "admin", password to "password" + --install-plugins Install blueprint plugins on ALL custom environments + --install-plugins=, Install blueprint plugins on specific custom environments + --only= Run only named environments (comma-separated) + --headed Launch browser in headed mode for debugging + --help Show this help message + +Built-in environments: ${ ALL_ENVIRONMENTS.map( ( e ) => e.name ).join( ', ' ) } + +Examples: + # Benchmark a single custom WordPress site + npm run benchmark -- --custom=my-site,http://localhost:10003 + + # Two custom sites: bare vs with plugins + npm run benchmark -- \\ + --custom=local-bare,http://localhost:10003 \\ + --custom=local-plugins,http://localhost:10004 \\ + --install-plugins=local-plugins + + # Custom site with non-default credentials + npm run benchmark -- --custom=my-site,http://localhost:10003,admin,secret + + # Compare Studio vs a custom site + npm run benchmark -- --only=studio,my-site --custom=my-site,http://localhost:10003 --rounds=3 ` ); } @@ -516,7 +581,13 @@ async function runBenchmark( } try { const result = await Promise.race( [ - measureSiteEditor( { url: benchmarkUrl, isPlaygroundWeb, isPlaygroundCli, headed } ), + measureSiteEditor( { + url: benchmarkUrl, + isPlaygroundWeb, + isPlaygroundCli, + credentials: env.credentials, + headed, + } ), sleep( MEASUREMENT_TIMEOUT ).then( () => { throw new Error( 'Measurement timed out' ); } ), @@ -620,6 +691,21 @@ function saveResultsSummary( results: BenchmarkResult[] ): void { // --------------------------------------------------------------------------- async function ensureStudioCLIBuilt(): Promise< boolean > { + // Ensure CLI dependencies are installed (required for the Vite build to resolve pm2-axon, etc.) + const cliNodeModules = path.resolve( STUDIO_ROOT, 'apps/cli', 'node_modules' ); + if ( ! fs.existsSync( cliNodeModules ) ) { + console.log( chalk.yellow( ' Installing CLI dependencies...' ) ); + try { + execSync( 'npm install', { + cwd: path.resolve( STUDIO_ROOT, 'apps/cli' ), + stdio: 'inherit', + } ); + } catch { + console.error( chalk.red( ' Failed to install CLI dependencies' ) ); + return false; + } + } + if ( ! fs.existsSync( STUDIO_CLI_PATH ) ) { console.log( chalk.yellow( ' Building Studio CLI...' ) ); try { @@ -655,7 +741,23 @@ async function main() { const opts = parseArgs(); // Filter environments - let environments = ALL_ENVIRONMENTS; + let environments = [ ...ALL_ENVIRONMENTS ]; + + // Add custom environments from --custom flags + for ( const custom of opts.customs ) { + const shouldInstallPlugins = + opts.installPlugins === 'all' || + ( Array.isArray( opts.installPlugins ) && opts.installPlugins.includes( custom.name ) ); + + environments.push( { + name: custom.name, + type: 'custom', + plugins: shouldInstallPlugins, + multiWorker: false, + customUrl: custom.url, + credentials: { username: custom.user, password: custom.password }, + } ); + } if ( opts.only.length > 0 ) { environments = environments.filter( ( e ) => opts.only.includes( e.name ) ); @@ -744,6 +846,23 @@ async function main() { const setup = await setupPlaygroundCliSite( env, port ); benchmarkUrl = setup.url; teardownFn = () => teardownPlaygroundCliSite( setup.process, port, setup.siteDir ); + } else if ( env.type === 'custom' ) { + benchmarkUrl = env.customUrl!; + console.log( chalk.gray( ` URL: ${ benchmarkUrl }` ) ); + + if ( env.plugins && env.credentials ) { + console.log( chalk.gray( ` Installing plugins via REST API...` ) ); + try { + await installPlugins( + benchmarkUrl, + PLUGINS_BLUEPRINT_PATH, + env.credentials.username, + env.credentials.password + ); + } catch ( err ) { + console.warn( chalk.yellow( ` Plugin installation failed: ${ err }` ) ); + } + } } else { benchmarkUrl = getPlaygroundWebUrl( env ); console.log( chalk.gray( ` URL: ${ benchmarkUrl.slice( 0, 80 ) }...` ) ); diff --git a/tools/benchmark-site-editor/install-plugins.ts b/tools/benchmark-site-editor/install-plugins.ts new file mode 100644 index 0000000000..e0176c382d --- /dev/null +++ b/tools/benchmark-site-editor/install-plugins.ts @@ -0,0 +1,193 @@ +/** + * Plugin installer for benchmark sites via the WordPress REST API. + * + * Reads plugin slugs from the shared plugins-blueprint.json + * (single source of truth for all environments). + * + * Flow: wp-login.php → nonce extraction → POST /wp-json/wp/v2/plugins + */ + +/* eslint-disable no-console */ + +import fs from 'fs'; +import chalk from 'chalk'; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +interface BlueprintStep { + step: string; + pluginData?: { + resource: string; + slug: string; + }; +} + +interface Blueprint { + steps: BlueprintStep[]; +} + +// --------------------------------------------------------------------------- +// Blueprint parsing +// --------------------------------------------------------------------------- + +/** + * Parse the blueprint JSON to extract plugin slugs. + * This is the single source of truth for which plugins to install across + * all environments (Studio, Playground CLI, Playground Web, custom). + */ +export function getPluginSlugsFromBlueprint( blueprintPath: string ): string[] { + const blueprint: Blueprint = JSON.parse( fs.readFileSync( blueprintPath, 'utf-8' ) ); + return blueprint.steps + .filter( ( step ) => step.step === 'installPlugin' && step.pluginData?.slug ) + .map( ( step ) => step.pluginData!.slug ); +} + +// --------------------------------------------------------------------------- +// WordPress REST API helpers +// --------------------------------------------------------------------------- + +/** + * Log in to WordPress via wp-login.php and return the auth cookies. + */ +async function wpLogin( siteUrl: string, username: string, password: string ): Promise< string > { + const loginUrl = `${ siteUrl }/wp-login.php`; + const body = new URLSearchParams( { + log: username, + pwd: password, + 'wp-submit': 'Log In', + redirect_to: '/wp-admin/', + testcookie: '1', + } ); + + const response = await fetch( loginUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Cookie: 'wordpress_test_cookie=WP%20Cookie%20check', + }, + body: body.toString(), + redirect: 'manual', + } ); + + const setCookieHeaders = response.headers.getSetCookie(); + const cookies = setCookieHeaders + .map( ( header ) => header.split( ';' )[ 0 ] ) + .filter( ( c ) => c.startsWith( 'wordpress_' ) ); + + if ( cookies.length === 0 ) { + throw new Error( + `WordPress login failed: no auth cookies returned (HTTP ${ response.status })` + ); + } + + return cookies.join( '; ' ); +} + +/** + * Extract the WP REST API nonce from the wp-admin page. + */ +async function extractNonce( siteUrl: string, cookies: string ): Promise< string > { + const response = await fetch( `${ siteUrl }/wp-admin/`, { + headers: { Cookie: cookies }, + redirect: 'follow', + } ); + + const html = await response.text(); + const match = html.match( /var\s+wpApiSettings\s*=\s*(\{[^;]+\});/ ); + if ( ! match ) { + throw new Error( 'Could not extract wpApiSettings nonce from wp-admin' ); + } + + const settings = JSON.parse( match[ 1 ] ); + if ( ! settings.nonce ) { + throw new Error( 'wpApiSettings found but nonce is missing' ); + } + + return settings.nonce as string; +} + +/** + * Install and activate a single plugin via the WP REST API. + */ +async function installPlugin( + siteUrl: string, + cookies: string, + nonce: string, + slug: string +): Promise< void > { + const response = await fetch( `${ siteUrl }/wp-json/wp/v2/plugins`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Cookie: cookies, + 'X-WP-Nonce': nonce, + }, + body: JSON.stringify( { slug, status: 'active' } ), + } ); + + if ( ! response.ok ) { + const body = await response.text(); + let json: { code?: string } | null = null; + try { + json = JSON.parse( body ); + } catch { + // Not JSON, fall through to throw + } + if ( json?.code === 'folder_exists' ) { + console.log( chalk.gray( ` ${ slug } already installed, skipping` ) ); + return; + } + throw new Error( `HTTP ${ response.status }: ${ body.slice( 0, 200 ) }` ); + } +} + +// --------------------------------------------------------------------------- +// Plugin installation +// --------------------------------------------------------------------------- + +/** + * Installs and activates plugins on a WordPress site using the REST API. + * + * Reads plugin slugs from the provided blueprint file (the same file used + * for Studio and Playground environments), logs in via wp-login.php, + * extracts a REST API nonce, and installs each plugin via + * POST /wp-json/wp/v2/plugins. + * + * @param siteUrl - The site's base URL (e.g., http://localhost:10003) + * @param blueprintPath - Path to plugins-blueprint.json + * @param username - WordPress admin username + * @param password - WordPress admin password + */ +export async function installPlugins( + siteUrl: string, + blueprintPath: string, + username: string, + password: string +): Promise< void > { + const slugs = getPluginSlugsFromBlueprint( blueprintPath ); + if ( slugs.length === 0 ) { + console.log( chalk.yellow( ' No plugins found in blueprint' ) ); + return; + } + + console.log( chalk.gray( ` Logging in to WordPress...` ) ); + const cookies = await wpLogin( siteUrl, username, password ); + + console.log( chalk.gray( ` Extracting REST API nonce...` ) ); + const nonce = await extractNonce( siteUrl, cookies ); + + console.log( chalk.gray( ` Installing ${ slugs.length } plugins via REST API...` ) ); + + for ( const slug of slugs ) { + try { + console.log( chalk.gray( ` ${ slug }...` ) ); + await installPlugin( siteUrl, cookies, nonce, slug ); + } catch ( err ) { + const message = err instanceof Error ? err.message : String( err ); + console.warn( chalk.yellow( ` Failed to install ${ slug }: ${ message }` ) ); + // Continue with remaining plugins + } + } +} diff --git a/tools/benchmark-site-editor/measure-site-editor.ts b/tools/benchmark-site-editor/measure-site-editor.ts index 52a935c101..c61920aa36 100644 --- a/tools/benchmark-site-editor/measure-site-editor.ts +++ b/tools/benchmark-site-editor/measure-site-editor.ts @@ -31,6 +31,8 @@ export interface MeasureOptions { isPlaygroundWeb: boolean; /** Whether this is a local Playground CLI site (127.0.0.1). Affects login flow. */ isPlaygroundCli: boolean; + /** WordPress admin credentials for wp-login.php authentication. When provided, logs in via the standard WordPress login form. */ + credentials?: { username: string; password: string }; /** Launch browser in headed mode for debugging. */ headed?: boolean; } @@ -97,7 +99,7 @@ function findEditorCanvasFrame( * The browser is always closed, even on error. */ export async function measureSiteEditor( options: MeasureOptions ): Promise< MeasurementResult > { - const { isPlaygroundWeb, isPlaygroundCli } = options; + const { isPlaygroundWeb, isPlaygroundCli, credentials } = options; // Normalize URL let wpAdminUrl = options.url; @@ -126,11 +128,6 @@ export async function measureSiteEditor( options: MeasureOptions ): Promise< Mea .frameLocator( 'iframe' ) .first(); - await wordPressFrameLocator - .getByRole( 'menuitem', { name: 'My WordPress Website' } ) - .first() - .click( { timeout: 30_000 } ); - await wordPressFrameLocator .getByRole( 'link', { name: 'Appearance' } ) .waitFor( { timeout: 30_000 } ); @@ -142,15 +139,30 @@ export async function measureSiteEditor( options: MeasureOptions ): Promise< Mea timeout: 120_000, } ); await page.waitForLoadState( 'networkidle', { timeout: 30_000 } ).catch( () => {} ); - - // Playground CLI may redirect to wp-login.php — handle login - if ( page.url().includes( 'wp-login.php' ) ) { - await page.fill( '#user_login', 'admin' ); - await page.fill( '#user_pass', 'password' ); - await page.click( '#wp-submit' ); - await page.waitForLoadState( 'networkidle', { timeout: 30_000 } ).catch( () => {} ); - } - + await page.getByRole( 'link', { name: 'Appearance' } ).waitFor( { + state: 'visible', + timeout: 60_000, + } ); + } else if ( credentials ) { + // Standard WordPress login via wp-login.php + await page.goto( `${ wpAdminUrl }/wp-login.php`, { + waitUntil: 'domcontentloaded', + timeout: 120_000, + } ); + await page.waitForLoadState( 'networkidle', { timeout: 30_000 } ).catch( () => {} ); + await page.fill( '#user_login', credentials.username ); + await page.fill( '#user_pass', credentials.password ); + await Promise.all( [ + page.waitForURL( '**/wp-admin/**', { timeout: 60_000 } ), + page.click( '#wp-submit' ), + ] ); + // Navigate explicitly to wp-admin to bypass plugin setup wizard redirects + // (WooCommerce, Jetpack, etc. redirect to their own onboarding pages) + await page.goto( `${ wpAdminUrl }/wp-admin/`, { + waitUntil: 'domcontentloaded', + timeout: 60_000, + } ); + await page.waitForLoadState( 'networkidle', { timeout: 30_000 } ).catch( () => {} ); await page.getByRole( 'link', { name: 'Appearance' } ).waitFor( { state: 'visible', timeout: 60_000, diff --git a/tools/benchmark-site-editor/package.json b/tools/benchmark-site-editor/package.json index 1083a94ead..c1584dc69a 100644 --- a/tools/benchmark-site-editor/package.json +++ b/tools/benchmark-site-editor/package.json @@ -6,16 +6,14 @@ "license": "GPLv2", "type": "module", "scripts": { - "benchmark": "ts-node ./benchmark.ts" - }, - "dependencies": { - "@wp-playground/cli": "3.1.2", - "chalk": "^5.3.0", - "playwright": "^1.58.1", - "ts-node": "^10.9.2" + "benchmark": "tsx benchmark.ts" }, "devDependencies": { "@types/node": "^25.3.2", + "@wp-playground/cli": "^3.0.22", + "chalk": "^5.3.0", + "playwright": "^1.58.1", + "tsx": "^4.7.0", "typescript": "^5.0.0" } } diff --git a/tools/benchmark-site-editor/tsconfig.json b/tools/benchmark-site-editor/tsconfig.json index 776319fecb..8f21fe7532 100644 --- a/tools/benchmark-site-editor/tsconfig.json +++ b/tools/benchmark-site-editor/tsconfig.json @@ -1,15 +1,15 @@ { - "extends": "../../tsconfig.base.json", "compilerOptions": { - "composite": true, - "baseUrl": "../..", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "allowImportingTsExtensions": true, - "outDir": "dist", - "declaration": true, - "emitDeclarationOnly": true + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "outDir": "./dist", + "rootDir": ".", + "declaration": false, + "sourceMap": false }, - "include": [ "**/*" ], - "exclude": [ "**/__mocks__/**/*", "**/node_modules/**/*", "**/dist/**/*", "**/out/**/*" ] + "include": [ "*.ts" ] }