diff --git a/.github/workflows/playwright.yml.disabled b/.github/workflows/playwright.yml.disabled
new file mode 100644
index 0000000000..3eb13143c3
--- /dev/null
+++ b/.github/workflows/playwright.yml.disabled
@@ -0,0 +1,27 @@
+name: Playwright Tests
+on:
+ push:
+ branches: [ main, master ]
+ pull_request:
+ branches: [ main, master ]
+jobs:
+ test:
+ timeout-minutes: 60
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: lts/*
+ - name: Install dependencies
+ run: npm ci
+ - name: Install Playwright Browsers
+ run: npx playwright install --with-deps
+ - name: Run Playwright tests
+ run: npx playwright test
+ - uses: actions/upload-artifact@v4
+ if: ${{ !cancelled() }}
+ with:
+ name: playwright-report
+ path: playwright-report/
+ retention-days: 30
diff --git a/.gitignore b/.gitignore
index c76c5355a3..fab667d302 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,10 @@ public/p5.min.js
# optional local preferences for vscode
local.code-workspace
.zed
+
+# Playwright
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
+/playwright/.auth/
diff --git a/package-lock.json b/package-lock.json
index 8f4304b4c5..011de71836 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -32,6 +32,7 @@
},
"devDependencies": {
"@codemirror/lang-javascript": "^6.2.2",
+ "@playwright/test": "^1.58.2",
"@preact/preset-vite": "^2.8.2",
"@swc/html": "^1.10.9",
"@testing-library/preact": "^3.2.3",
@@ -2020,15 +2021,6 @@
"node": ">=16"
}
},
- "node_modules/@cloudflare/workers-types": {
- "version": "4.20260316.1",
- "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260316.1.tgz",
- "integrity": "sha512-HUZ+vQD8/1A4Fz/8WAlzYWcS5W5u3Nu7Dv9adkIkmLfeKqMIRn01vc4nSUBar60KkmohyQHkPi8jtWV/zazvAg==",
- "dev": true,
- "license": "MIT OR Apache-2.0",
- "optional": true,
- "peer": true
- },
"node_modules/@codemirror/autocomplete": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz",
@@ -4226,6 +4218,22 @@
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"license": "MIT"
},
+ "node_modules/@playwright/test": {
+ "version": "1.58.2",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
+ "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.58.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@poppinss/colors": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz",
@@ -18879,6 +18887,53 @@
"pathe": "^2.0.1"
}
},
+ "node_modules/playwright": {
+ "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"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "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"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "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,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/points-on-curve": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz",
@@ -23714,6 +23769,7 @@
"integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"pathe": "^2.0.3"
}
@@ -26272,6 +26328,7 @@
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"workerd": "bin/workerd"
},
@@ -26321,490 +26378,6 @@
}
}
},
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/wrangler/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/wrap-ansi": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
diff --git a/package.json b/package.json
index 05da1278ba..72c544ea33 100644
--- a/package.json
+++ b/package.json
@@ -59,6 +59,7 @@
},
"devDependencies": {
"@codemirror/lang-javascript": "^6.2.2",
+ "@playwright/test": "^1.58.2",
"@preact/preset-vite": "^2.8.2",
"@swc/html": "^1.10.9",
"@testing-library/preact": "^3.2.3",
diff --git a/playwright-tests/find-broken-sketches-on-beta.spec.ts b/playwright-tests/find-broken-sketches-on-beta.spec.ts
new file mode 100644
index 0000000000..27716a34e8
--- /dev/null
+++ b/playwright-tests/find-broken-sketches-on-beta.spec.ts
@@ -0,0 +1,149 @@
+// A playwright test to load various reference page URLs, scroll through for any
+// sketches, and monitor the console, failing if errors are reported in the console
+// (with some allowed).
+//
+//How to run: headed: npx playwright test --project=chromium --headed
+//How to run: with UI*: npx playwright test --project=chromium --ui
+//How to run: no UI*: npx playwright test --project=chromium
+//\* these headless modes still have issues with webgl.
+//
+//TODO: rate-limit so we don't hassle the server or more likely get ip-banned by cloudflare CDN
+//TODO: prefetching gets in the way, when links don't have a trailing slash!
+//TODO: consider turning off browser prefetching to lessen request rates.
+//TODO: fix webgl for headless (chromium). "Error creating webgl context at ..."
+// It's not enough to LOAD the containing page, the browser has to scroll so the sketch is in viewport to get loaded. (and we have to wait a little, especially in the case of shader compilation)
+// TODO: can we cause the browser to load and start all the sketches, without needing them to be in view?
+// TODO: can we detect and scroll directly to each sketch in turn (waiting 1sec once there), rather than just scrolling through?
+// TODO: test that a bad initial link is correctly reported as such (this could also legitimately be that a ref page has been renamed/removed)
+
+import { test, type Page, type ConsoleMessage, expect } from "@playwright/test";
+import { referencePageURLPaths } from "./reference-links.ts";
+
+type LogSeverity = "exception" | "error" | "other";
+type LogMsg = { url: string, text: string, severity: LogSeverity };
+type LogPolicy = "skipSilently" | "skipButMention" | "log";
+type Config = {
+ shouldLogNonErrorLogsInline: boolean,
+ on404ConsoleMsg: LogPolicy
+}
+
+const config: Config = {
+ shouldLogNonErrorLogsInline: false,
+ on404ConsoleMsg: "skipButMention"
+}
+
+ //TODO: extract this from astro build or data.json
+ function getURLPathsToTest(): string[] {
+ return [
+ "/reference/p5/intentionallyBadURL/",
+ //on local this will also 404 - no trailing slash
+ "/reference/p5/square",
+ //locally modded up to have some errors
+ "/reference/p5/filterColor/",
+ //the full set
+ ...referencePageURLPaths
+ ]
+ }
+
+
+/**
+ * Attaches listeners to capture console errors, uncaught exceptions. (also non-error console msgs)
+ * @param page
+ * @param errorLog An array to store captured error messages from the console
+ * @param nonErrorLog An array to store captured non-error messages from the console
+ * @todo do we need to explicitly drop this event handler when we nav away from the page?
+ */
+function setupErrorTracking(page: Page, nonErrorLog: LogMsg[], errorLog: LogMsg[]): void {
+ //1. get the console error and others (but I think this _doesn't_ get exceptions - later)
+ page.on("console", (msg: ConsoleMessage) => {
+ if (msg.type() === "error") {
+ //TODO: generalise to allow filtering of error types we don't care about
+ //is this an error we probably don't care about?
+ if (msg.text().includes("Failed to load resource: the server responded with a status of 404") && config.on404ConsoleMsg !== "log") {
+ if (config.on404ConsoleMsg === "skipButMention") {
+ console.log("Skipping a (maybe) expected 404 - often a prefetch with no trailing slash?")
+ }
+ } else {
+ //log error normally
+ errorLog.push({ severity: "error", text: msg.text(), url: page.url() });
+ }
+ } else {
+ //non error console log
+ nonErrorLog.push({ severity: "other", url: page.url(), text: msg.text() });
+ if (config.shouldLogNonErrorLogsInline) {
+ console.log(`[Console non-error] ${msg.text()} at ${page.url()}`);
+ }
+ }
+ }
+ );
+ //2. also register for exceptions
+ page.on("pageerror", (err: Error) => {
+ errorLog.push({ severity: "exception", text: err.message, url: page.url() });
+ });
+}
+
+test("look for sketch console errors in ref pages", async ({ page }) => {
+ test.setTimeout(60 * 60 * 1000);//60 minutes
+ const pathsToCheck: string[] = getURLPathsToTest();
+
+
+ const allErrors: LogMsg[] = [];
+
+ const errorsInPage: LogMsg[] = [];
+ const nonErrorsInPage: LogMsg[] = [];
+
+ setupErrorTracking(page, nonErrorsInPage, errorsInPage);
+
+ for (const path of pathsToCheck) {
+ const baseURL = "http://localhost:4321"; //"https://beta.p5js.org"
+ const url = `${baseURL}${path}`;
+ console.log("going to url: ", url)
+ await page.goto(url);
+ await page.waitForTimeout(500);
+ await scrollToBottom(page);
+ // Give p5.js setup() and draw() time to execute, and shader compilation!
+ console.log("done scrolling to bottom. waiting for 1500")
+
+ //TODO: this is a weak point. no idea how long it could take for everything to load (e.g. compile shader). can we detect iframe's p5 setup completion? maybe with a small instrumentation change to p5?
+ await page.waitForTimeout(1500);
+
+ if (errorsInPage.length > 0) {
+ console.error(`Errors on ${url}:`, errorsInPage);
+
+ allErrors.push(...errorsInPage);
+ errorsInPage.length = 0; // Clear for next page
+ } else {
+ console.log(`no errors on ${url}`);
+ }
+ }
+
+ //TODO: perhaps provide non-error console outputs for the url(s) which had errors, for more context.
+ //Also take screenshots of them?
+
+ expect(allErrors).toHaveLength(0);
+});
+
+// We need to scroll slowly or otherwise make sure all sketches come into viewport to start loading. THEN we need to wait a bit!
+// Modified (debugged) from damaon's solution at https://github.com/microsoft/playwright/issues/4302#issuecomment-1882853669
+// There are other solutions we might consider at the same url https://github.com/microsoft/playwright/issues/4302
+async function scrollToBottom(page: Page) {
+ const [scrollY, scrollHeight] = await page.evaluate(() => [
+ window.scrollY,
+ window.document.documentElement.scrollHeight,
+ ])
+ // console.log(`window.scrollY at start: ${scrollY} window scrollHeight: ${scrollHeight}`)
+
+ //TODO: are we going to pay attn to initial scrollY or not?
+ for (let i = 0; scrollY + i < scrollHeight; i += 300) {
+ await page.evaluate((i) => {
+ // console.log("scrolling to: ", i)
+ // TODO: check. this should be scrolling to i + scrollY if we're going to pay attn to the latter
+ window.scrollTo({ top: i, left: 0, behavior: 'smooth' })
+ }, i)
+ await sleep(0.1)
+ }
+}
+
+function sleep(seconds: number) {
+ return new Promise((resolve) => setTimeout(resolve, seconds * 1000))
+}
\ No newline at end of file
diff --git a/playwright-tests/reference-links.ts b/playwright-tests/reference-links.ts
new file mode 100644
index 0000000000..cfb883c537
--- /dev/null
+++ b/playwright-tests/reference-links.ts
@@ -0,0 +1,883 @@
+// This is a temporary file for early testing of playwright to find broken sketches in reference pages
+// eventually these urls would come from the build process (astro's or /docs/reference/data.json)
+// There are some paths here which do not end up having a page generated. Including:
+// "/reference/p5/types/Array/",
+// "/reference/p5/types/Boolean/",
+// "/reference/p5/types/Number/",
+// "/reference/p5/types/Object/",
+// "/reference/p5/types/String/",
+export const referencePageURLPaths = [
+ "/reference/p5.Amplitude/getLevel/",
+ "/reference/p5.Amplitude/setInput/",
+ "/reference/p5.Amplitude/smooth/",
+ "/reference/p5.AudioIn/start/",
+ "/reference/p5.AudioIn/stop/",
+ "/reference/p5.Biquad/freq/",
+ "/reference/p5.Biquad/gain/",
+ "/reference/p5.Biquad/res/",
+ "/reference/p5.Biquad/setType/",
+ "/reference/p5.Camera/camera/",
+ "/reference/p5.Camera/centerX/",
+ "/reference/p5.Camera/centerY/",
+ "/reference/p5.Camera/centerZ/",
+ "/reference/p5.Camera/eyeX/",
+ "/reference/p5.Camera/eyeY/",
+ "/reference/p5.Camera/eyeZ/",
+ "/reference/p5.Camera/frustum/",
+ "/reference/p5.Camera/lookAt/",
+ "/reference/p5.Camera/move/",
+ "/reference/p5.Camera/ortho/",
+ "/reference/p5.Camera/pan/",
+ "/reference/p5.Camera/perspective/",
+ "/reference/p5.Camera/set/",
+ "/reference/p5.Camera/setPosition/",
+ "/reference/p5.Camera/slerp/",
+ "/reference/p5.Camera/tilt/",
+ "/reference/p5.Camera/upX/",
+ "/reference/p5.Camera/upY/",
+ "/reference/p5.Camera/upZ/",
+ "/reference/p5.Color/contrast/",
+ "/reference/p5.Color/setAlpha/",
+ "/reference/p5.Color/setBlue/",
+ "/reference/p5.Color/setGreen/",
+ "/reference/p5.Color/setRed/",
+ "/reference/p5.Color/toString/",
+ "/reference/p5.Delay/delayTime/",
+ "/reference/p5.Delay/feedback/",
+ "/reference/p5.Delay/process/",
+ "/reference/p5.Element/addClass/",
+ "/reference/p5.Element/attribute/",
+ "/reference/p5.Element/center/",
+ "/reference/p5.Element/changed/",
+ "/reference/p5.Element/child/",
+ "/reference/p5.Element/class/",
+ "/reference/p5.Element/doubleClicked/",
+ "/reference/p5.Element/dragLeave/",
+ "/reference/p5.Element/dragOver/",
+ "/reference/p5.Element/draggable/",
+ "/reference/p5.Element/drop/",
+ "/reference/p5.Element/elt/",
+ "/reference/p5.Element/hasClass/",
+ "/reference/p5.Element/height/",
+ "/reference/p5.Element/hide/",
+ "/reference/p5.Element/html/",
+ "/reference/p5.Element/id/",
+ "/reference/p5.Element/input/",
+ "/reference/p5.Element/mouseClicked/",
+ "/reference/p5.Element/mouseMoved/",
+ "/reference/p5.Element/mouseOut/",
+ "/reference/p5.Element/mouseOver/",
+ "/reference/p5.Element/mousePressed/",
+ "/reference/p5.Element/mouseReleased/",
+ "/reference/p5.Element/mouseWheel/",
+ "/reference/p5.Element/parent/",
+ "/reference/p5.Element/position/",
+ "/reference/p5.Element/remove/",
+ "/reference/p5.Element/removeAttribute/",
+ "/reference/p5.Element/removeClass/",
+ "/reference/p5.Element/show/",
+ "/reference/p5.Element/size/",
+ "/reference/p5.Element/style/",
+ "/reference/p5.Element/toggleClass/",
+ "/reference/p5.Element/value/",
+ "/reference/p5.Element/width/",
+ "/reference/p5.Envelope/attackTime/",
+ "/reference/p5.Envelope/play/",
+ "/reference/p5.Envelope/releaseTime/",
+ "/reference/p5.Envelope/setADSR/",
+ "/reference/p5.Envelope/setInput/",
+ "/reference/p5.Envelope/triggerAttack/",
+ "/reference/p5.Envelope/triggerRelease/",
+ "/reference/p5.FFT/analyze/",
+ "/reference/p5.FFT/waveform/",
+ "/reference/p5.File/data/",
+ "/reference/p5.File/file/",
+ "/reference/p5.File/name/",
+ "/reference/p5.File/size/",
+ "/reference/p5.File/subtype/",
+ "/reference/p5.File/type/",
+ "/reference/p5.Font/textToContours/",
+ "/reference/p5.Font/textToModel/",
+ "/reference/p5.Font/textToPaths/",
+ "/reference/p5.Font/textToPoints/",
+ "/reference/p5.Framebuffer/autoSized/",
+ "/reference/p5.Framebuffer/begin/",
+ "/reference/p5.Framebuffer/color/",
+ "/reference/p5.Framebuffer/createCamera/",
+ "/reference/p5.Framebuffer/depth/",
+ "/reference/p5.Framebuffer/draw/",
+ "/reference/p5.Framebuffer/end/",
+ "/reference/p5.Framebuffer/get/",
+ "/reference/p5.Framebuffer/height/",
+ "/reference/p5.Framebuffer/loadPixels/",
+ "/reference/p5.Framebuffer/pixelDensity/",
+ "/reference/p5.Framebuffer/pixels/",
+ "/reference/p5.Framebuffer/remove/",
+ "/reference/p5.Framebuffer/resize/",
+ "/reference/p5.Framebuffer/width/",
+ "/reference/p5.Geometry/calculateBoundingBox/",
+ "/reference/p5.Geometry/clearColors/",
+ "/reference/p5.Geometry/computeFaces/",
+ "/reference/p5.Geometry/computeNormals/",
+ "/reference/p5.Geometry/faces/",
+ "/reference/p5.Geometry/flipU/",
+ "/reference/p5.Geometry/flipV/",
+ "/reference/p5.Geometry/gid/",
+ "/reference/p5.Geometry/makeEdgesFromFaces/",
+ "/reference/p5.Geometry/normalize/",
+ "/reference/p5.Geometry/uvs/",
+ "/reference/p5.Geometry/vertexNormals/",
+ "/reference/p5.Geometry/vertexProperty/",
+ "/reference/p5.Geometry/vertices/",
+ "/reference/p5.Graphics/createFramebuffer/",
+ "/reference/p5.Graphics/remove/",
+ "/reference/p5.Graphics/reset/",
+ "/reference/p5.Image/blend/",
+ "/reference/p5.Image/copy/",
+ "/reference/p5.Image/delay/",
+ "/reference/p5.Image/filter/",
+ "/reference/p5.Image/get/",
+ "/reference/p5.Image/getCurrentFrame/",
+ "/reference/p5.Image/height/",
+ "/reference/p5.Image/loadPixels/",
+ "/reference/p5.Image/mask/",
+ "/reference/p5.Image/numFrames/",
+ "/reference/p5.Image/pause/",
+ "/reference/p5.Image/pixelDensity/",
+ "/reference/p5.Image/pixels/",
+ "/reference/p5.Image/play/",
+ "/reference/p5.Image/reset/",
+ "/reference/p5.Image/resize/",
+ "/reference/p5.Image/save/",
+ "/reference/p5.Image/set/",
+ "/reference/p5.Image/setFrame/",
+ "/reference/p5.Image/updatePixels/",
+ "/reference/p5.Image/width/",
+ "/reference/p5.MediaElement/addCue/",
+ "/reference/p5.MediaElement/autoplay/",
+ "/reference/p5.MediaElement/clearCues/",
+ "/reference/p5.MediaElement/connect/",
+ "/reference/p5.MediaElement/disconnect/",
+ "/reference/p5.MediaElement/duration/",
+ "/reference/p5.MediaElement/hideControls/",
+ "/reference/p5.MediaElement/loop/",
+ "/reference/p5.MediaElement/noLoop/",
+ "/reference/p5.MediaElement/onended/",
+ "/reference/p5.MediaElement/pause/",
+ "/reference/p5.MediaElement/play/",
+ "/reference/p5.MediaElement/removeCue/",
+ "/reference/p5.MediaElement/showControls/",
+ "/reference/p5.MediaElement/speed/",
+ "/reference/p5.MediaElement/src/",
+ "/reference/p5.MediaElement/stop/",
+ "/reference/p5.MediaElement/time/",
+ "/reference/p5.MediaElement/volume/",
+ "/reference/p5.Noise/type/",
+ "/reference/p5.Oscillator/freq/",
+ "/reference/p5.Oscillator/phase/",
+ "/reference/p5.Oscillator/setType/",
+ "/reference/p5.Panner/pan/",
+ "/reference/p5.Panner3D/maxDist/",
+ "/reference/p5.Panner3D/positionX/",
+ "/reference/p5.Panner3D/positionY/",
+ "/reference/p5.Panner3D/positionZ/",
+ "/reference/p5.Panner3D/process/",
+ "/reference/p5.Panner3D/rolloff/",
+ "/reference/p5.Panner3D/set/",
+ "/reference/p5.Panner3D/setFalloff/",
+ "/reference/p5.PitchShifter/shift/",
+ "/reference/p5.Reverb/set/",
+ "/reference/p5.Shader/copyToContext/",
+ "/reference/p5.Shader/inspectHooks/",
+ "/reference/p5.Shader/modify/",
+ "/reference/p5.Shader/setUniform/",
+ "/reference/p5.Shader/version/",
+ "/reference/p5.SoundFile/channels/",
+ "/reference/p5.SoundFile/duration/",
+ "/reference/p5.SoundFile/frames/",
+ "/reference/p5.SoundFile/isLooping/",
+ "/reference/p5.SoundFile/isPlaying/",
+ "/reference/p5.SoundFile/jump/",
+ "/reference/p5.SoundFile/loop/",
+ "/reference/p5.SoundFile/onended/",
+ "/reference/p5.SoundFile/pause/",
+ "/reference/p5.SoundFile/play/",
+ "/reference/p5.SoundFile/rate/",
+ "/reference/p5.SoundFile/sampleRate/",
+ "/reference/p5.SoundFile/setLoop/",
+ "/reference/p5.SoundFile/setPath/",
+ "/reference/p5.SoundFile/start/",
+ "/reference/p5.SoundFile/stop/",
+ "/reference/p5.Table/addColumn/",
+ "/reference/p5.Table/addRow/",
+ "/reference/p5.Table/clearRows/",
+ "/reference/p5.Table/columns/",
+ "/reference/p5.Table/findRow/",
+ "/reference/p5.Table/findRows/",
+ "/reference/p5.Table/get/",
+ "/reference/p5.Table/getArray/",
+ "/reference/p5.Table/getColumn/",
+ "/reference/p5.Table/getColumnCount/",
+ "/reference/p5.Table/getNum/",
+ "/reference/p5.Table/getObject/",
+ "/reference/p5.Table/getRow/",
+ "/reference/p5.Table/getRowCount/",
+ "/reference/p5.Table/getRows/",
+ "/reference/p5.Table/getString/",
+ "/reference/p5.Table/matchRow/",
+ "/reference/p5.Table/matchRows/",
+ "/reference/p5.Table/removeColumn/",
+ "/reference/p5.Table/removeRow/",
+ "/reference/p5.Table/removeTokens/",
+ "/reference/p5.Table/rows/",
+ "/reference/p5.Table/set/",
+ "/reference/p5.Table/setNum/",
+ "/reference/p5.Table/setString/",
+ "/reference/p5.Table/trim/",
+ "/reference/p5.TableRow/get/",
+ "/reference/p5.TableRow/getNum/",
+ "/reference/p5.TableRow/getString/",
+ "/reference/p5.TableRow/set/",
+ "/reference/p5.TableRow/setNum/",
+ "/reference/p5.TableRow/setString/",
+ "/reference/p5.Vector/add/",
+ "/reference/p5.Vector/angleBetween/",
+ "/reference/p5.Vector/array/",
+ "/reference/p5.Vector/clampToZero/",
+ "/reference/p5.Vector/copy/",
+ "/reference/p5.Vector/cross/",
+ "/reference/p5.Vector/dist/",
+ "/reference/p5.Vector/div/",
+ "/reference/p5.Vector/dot/",
+ "/reference/p5.Vector/equals/",
+ "/reference/p5.Vector/fromAngle/",
+ "/reference/p5.Vector/fromAngles/",
+ "/reference/p5.Vector/getValue/",
+ "/reference/p5.Vector/heading/",
+ "/reference/p5.Vector/lerp/",
+ "/reference/p5.Vector/limit/",
+ "/reference/p5.Vector/mag/",
+ "/reference/p5.Vector/magSq/",
+ "/reference/p5.Vector/mult/",
+ "/reference/p5.Vector/normalize/",
+ "/reference/p5.Vector/random2D/",
+ "/reference/p5.Vector/random3D/",
+ "/reference/p5.Vector/reflect/",
+ "/reference/p5.Vector/rem/",
+ "/reference/p5.Vector/rotate/",
+ "/reference/p5.Vector/set/",
+ "/reference/p5.Vector/setHeading/",
+ "/reference/p5.Vector/setMag/",
+ "/reference/p5.Vector/setValue/",
+ "/reference/p5.Vector/slerp/",
+ "/reference/p5.Vector/sub/",
+ "/reference/p5.Vector/toString/",
+ "/reference/p5.Vector/x/",
+ "/reference/p5.Vector/y/",
+ "/reference/p5.Vector/z/",
+ "/reference/p5.XML/addChild/",
+ "/reference/p5.XML/getAttributeCount/",
+ "/reference/p5.XML/getChild/",
+ "/reference/p5.XML/getChildren/",
+ "/reference/p5.XML/getContent/",
+ "/reference/p5.XML/getName/",
+ "/reference/p5.XML/getNum/",
+ "/reference/p5.XML/getParent/",
+ "/reference/p5.XML/getString/",
+ "/reference/p5.XML/hasAttribute/",
+ "/reference/p5.XML/hasChildren/",
+ "/reference/p5.XML/listAttributes/",
+ "/reference/p5.XML/listChildren/",
+ "/reference/p5.XML/removeChild/",
+ "/reference/p5.XML/serialize/",
+ "/reference/p5.XML/setAttribute/",
+ "/reference/p5.XML/setName/",
+ "/reference/p5.p5soundMixEffect/wet/",
+ "/reference/p5.p5soundNode/amp/",
+ "/reference/p5.p5soundSource/start/",
+ "/reference/p5.p5soundSource/stop/",
+ "/reference/p5.sound/p5.Amplitude/",
+ "/reference/p5.sound/p5.AudioIn/",
+ "/reference/p5.sound/p5.BandPass/",
+ "/reference/p5.sound/p5.Biquad/",
+ "/reference/p5.sound/p5.Delay/",
+ "/reference/p5.sound/p5.Envelope/",
+ "/reference/p5.sound/p5.FFT/",
+ "/reference/p5.sound/p5.Gain/",
+ "/reference/p5.sound/p5.HighPass/",
+ "/reference/p5.sound/p5.LowPass/",
+ "/reference/p5.sound/p5.Noise/",
+ "/reference/p5.sound/p5.Oscillator/",
+ "/reference/p5.sound/p5.Panner/",
+ "/reference/p5.sound/p5.Panner3D/",
+ "/reference/p5.sound/p5.PitchShifter/",
+ "/reference/p5.sound/p5.Reverb/",
+ "/reference/p5.sound/p5.SawOsc/",
+ "/reference/p5.sound/p5.SinOsc/",
+ "/reference/p5.sound/p5.SoundFile/",
+ "/reference/p5.sound/p5.SqrOsc/",
+ "/reference/p5.sound/p5.TriOsc/",
+ "/reference/p5.sound/p5.p5soundMixEffect/",
+ "/reference/p5.sound/p5.p5soundNode/",
+ "/reference/p5.sound/p5.p5soundSource/",
+ "/reference/p5/abs/",
+ "/reference/p5/accelerationX/",
+ "/reference/p5/accelerationY/",
+ "/reference/p5/accelerationZ/",
+ "/reference/p5/acos/",
+ "/reference/p5/addElement/",
+ "/reference/p5/alpha/",
+ "/reference/p5/ambientLight/",
+ "/reference/p5/ambientMaterial/",
+ "/reference/p5/angleMode/",
+ "/reference/p5/applyMatrix/",
+ "/reference/p5/arc/",
+ "/reference/p5/asin/",
+ "/reference/p5/async_await/",
+ "/reference/p5/atan/",
+ "/reference/p5/atan2/",
+ "/reference/p5/background/",
+ "/reference/p5/baseColorShader/",
+ "/reference/p5/baseFilterShader/",
+ "/reference/p5/baseMaterialShader/",
+ "/reference/p5/baseNormalShader/",
+ "/reference/p5/baseStrokeShader/",
+ "/reference/p5/beginClip/",
+ "/reference/p5/beginContour/",
+ "/reference/p5/beginShape/",
+ "/reference/p5/bezier/",
+ "/reference/p5/bezierOrder/",
+ "/reference/p5/bezierPoint/",
+ "/reference/p5/bezierTangent/",
+ "/reference/p5/bezierVertex/",
+ "/reference/p5/blend/",
+ "/reference/p5/blendMode/",
+ "/reference/p5/blue/",
+ "/reference/p5/boolean/",
+ "/reference/p5/box/",
+ "/reference/p5/brightness/",
+ "/reference/p5/buildColorShader/",
+ "/reference/p5/buildFilterShader/",
+ "/reference/p5/buildGeometry/",
+ "/reference/p5/buildMaterialShader/",
+ "/reference/p5/buildNormalShader/",
+ "/reference/p5/buildStrokeShader/",
+ "/reference/p5/byte/",
+ "/reference/p5/camera/",
+ "/reference/p5/cameraInputs/",
+ "/reference/p5/ceil/",
+ "/reference/p5/char/",
+ "/reference/p5/circle/",
+ "/reference/p5/class/",
+ "/reference/p5/clear/",
+ "/reference/p5/clearDepth/",
+ "/reference/p5/clearStorage/",
+ "/reference/p5/clip/",
+ "/reference/p5/close/",
+ "/reference/p5/code/",
+ "/reference/p5/color/",
+ "/reference/p5/colorMode/",
+ "/reference/p5/combineColors/",
+ "/reference/p5/cone/",
+ "/reference/p5/console/",
+ "/reference/p5/constants/ADD/",
+ "/reference/p5/constants/ALT/",
+ "/reference/p5/constants/ARROW/",
+ "/reference/p5/constants/AUDIO/",
+ "/reference/p5/constants/AUTO/",
+ "/reference/p5/constants/AXES/",
+ "/reference/p5/constants/BACKSPACE/",
+ "/reference/p5/constants/BASELINE/",
+ "/reference/p5/constants/BEVEL/",
+ "/reference/p5/constants/BEZIER/",
+ "/reference/p5/constants/BLEND/",
+ "/reference/p5/constants/BLUR/",
+ "/reference/p5/constants/BOLD/",
+ "/reference/p5/constants/BOLDITALIC/",
+ "/reference/p5/constants/BOTTOM/",
+ "/reference/p5/constants/BURN/",
+ "/reference/p5/constants/CENTER/",
+ "/reference/p5/constants/CHAR/",
+ "/reference/p5/constants/CHORD/",
+ "/reference/p5/constants/CLAMP/",
+ "/reference/p5/constants/CLOSE/",
+ "/reference/p5/constants/CONTAIN/",
+ "/reference/p5/constants/CONTROL/",
+ "/reference/p5/constants/CORNER/",
+ "/reference/p5/constants/CORNERS/",
+ "/reference/p5/constants/COVER/",
+ "/reference/p5/constants/CROSS/",
+ "/reference/p5/constants/CURVE/",
+ "/reference/p5/constants/DARKEST/",
+ "/reference/p5/constants/DEGREES/",
+ "/reference/p5/constants/DEG_TO_RAD/",
+ "/reference/p5/constants/DELETE/",
+ "/reference/p5/constants/DIFFERENCE/",
+ "/reference/p5/constants/DILATE/",
+ "/reference/p5/constants/DODGE/",
+ "/reference/p5/constants/DOWN_ARROW/",
+ "/reference/p5/constants/EMPTY_PATH/",
+ "/reference/p5/constants/ENTER/",
+ "/reference/p5/constants/ERODE/",
+ "/reference/p5/constants/ESCAPE/",
+ "/reference/p5/constants/EXCLUDE/",
+ "/reference/p5/constants/EXCLUSION/",
+ "/reference/p5/constants/FALLBACK/",
+ "/reference/p5/constants/FILL/",
+ "/reference/p5/constants/FLAT/",
+ "/reference/p5/constants/FLOAT/",
+ "/reference/p5/constants/FULL/",
+ "/reference/p5/constants/GRAY/",
+ "/reference/p5/constants/GRID/",
+ "/reference/p5/constants/HALF_FLOAT/",
+ "/reference/p5/constants/HALF_PI/",
+ "/reference/p5/constants/HAND/",
+ "/reference/p5/constants/HARD_LIGHT/",
+ "/reference/p5/constants/HSB/",
+ "/reference/p5/constants/HSL/",
+ "/reference/p5/constants/HWB/",
+ "/reference/p5/constants/IMAGE/",
+ "/reference/p5/constants/IMMEDIATE/",
+ "/reference/p5/constants/INCLUDE/",
+ "/reference/p5/constants/INVERT/",
+ "/reference/p5/constants/ITALIC/",
+ "/reference/p5/constants/LAB/",
+ "/reference/p5/constants/LABEL/",
+ "/reference/p5/constants/LANDSCAPE/",
+ "/reference/p5/constants/LCH/",
+ "/reference/p5/constants/LEFT/",
+ "/reference/p5/constants/LEFT_ARROW/",
+ "/reference/p5/constants/LIGHTEST/",
+ "/reference/p5/constants/LINEAR/",
+ "/reference/p5/constants/LINES/",
+ "/reference/p5/constants/LINE_LOOP/",
+ "/reference/p5/constants/LINE_STRIP/",
+ "/reference/p5/constants/MIRROR/",
+ "/reference/p5/constants/MITER/",
+ "/reference/p5/constants/MOVE/",
+ "/reference/p5/constants/MULTIPLY/",
+ "/reference/p5/constants/NEAREST/",
+ "/reference/p5/constants/NORMAL/",
+ "/reference/p5/constants/OKLAB/",
+ "/reference/p5/constants/OKLCH/",
+ "/reference/p5/constants/OPAQUE/",
+ "/reference/p5/constants/OPEN/",
+ "/reference/p5/constants/OPTION/",
+ "/reference/p5/constants/OVERLAY/",
+ "/reference/p5/constants/P2D/",
+ "/reference/p5/constants/P2DHDR/",
+ "/reference/p5/constants/PATH/",
+ "/reference/p5/constants/PI/",
+ "/reference/p5/constants/PIE/",
+ "/reference/p5/constants/POINTS/",
+ "/reference/p5/constants/PORTRAIT/",
+ "/reference/p5/constants/POSTERIZE/",
+ "/reference/p5/constants/PROJECT/",
+ "/reference/p5/constants/QUADRATIC/",
+ "/reference/p5/constants/QUADS/",
+ "/reference/p5/constants/QUAD_STRIP/",
+ "/reference/p5/constants/QUARTER_PI/",
+ "/reference/p5/constants/RADIANS/",
+ "/reference/p5/constants/RADIUS/",
+ "/reference/p5/constants/RAD_TO_DEG/",
+ "/reference/p5/constants/REMOVE/",
+ "/reference/p5/constants/REPEAT/",
+ "/reference/p5/constants/REPLACE/",
+ "/reference/p5/constants/RETURN/",
+ "/reference/p5/constants/RGB/",
+ "/reference/p5/constants/RGBA/",
+ "/reference/p5/constants/RGBHDR/",
+ "/reference/p5/constants/RIGHT/",
+ "/reference/p5/constants/RIGHT_ARROW/",
+ "/reference/p5/constants/ROUND/",
+ "/reference/p5/constants/SCREEN/",
+ "/reference/p5/constants/SHIFT/",
+ "/reference/p5/constants/SIMPLE/",
+ "/reference/p5/constants/SMOOTH/",
+ "/reference/p5/constants/SOFT_LIGHT/",
+ "/reference/p5/constants/SQUARE/",
+ "/reference/p5/constants/STROKE/",
+ "/reference/p5/constants/SUBTRACT/",
+ "/reference/p5/constants/TAB/",
+ "/reference/p5/constants/TAU/",
+ "/reference/p5/constants/TESS/",
+ "/reference/p5/constants/TEXT/",
+ "/reference/p5/constants/TEXTURE/",
+ "/reference/p5/constants/THRESHOLD/",
+ "/reference/p5/constants/TOP/",
+ "/reference/p5/constants/TRIANGLES/",
+ "/reference/p5/constants/TRIANGLE_FAN/",
+ "/reference/p5/constants/TRIANGLE_STRIP/",
+ "/reference/p5/constants/TWO_PI/",
+ "/reference/p5/constants/UNSIGNED_BYTE/",
+ "/reference/p5/constants/UNSIGNED_INT/",
+ "/reference/p5/constants/UP_ARROW/",
+ "/reference/p5/constants/VERSION/",
+ "/reference/p5/constants/VIDEO/",
+ "/reference/p5/constants/WAIT/",
+ "/reference/p5/constants/WEBGL/",
+ "/reference/p5/constants/WEBGL2/",
+ "/reference/p5/constants/WEBGPU/",
+ "/reference/p5/constants/WORD/",
+ "/reference/p5/constrain/",
+ "/reference/p5/copy/",
+ "/reference/p5/cos/",
+ "/reference/p5/createA/",
+ "/reference/p5/createAudio/",
+ "/reference/p5/createButton/",
+ "/reference/p5/createCamera/",
+ "/reference/p5/createCanvas/",
+ "/reference/p5/createCapture/",
+ "/reference/p5/createCheckbox/",
+ "/reference/p5/createColorPicker/",
+ "/reference/p5/createDiv/",
+ "/reference/p5/createElement/",
+ "/reference/p5/createFileInput/",
+ "/reference/p5/createFilterShader/",
+ "/reference/p5/createFramebuffer/",
+ "/reference/p5/createGraphics/",
+ "/reference/p5/createImage/",
+ "/reference/p5/createImg/",
+ "/reference/p5/createInput/",
+ "/reference/p5/createModel/",
+ "/reference/p5/createP/",
+ "/reference/p5/createRadio/",
+ "/reference/p5/createSelect/",
+ "/reference/p5/createShader/",
+ "/reference/p5/createSlider/",
+ "/reference/p5/createSpan/",
+ "/reference/p5/createVector/",
+ "/reference/p5/createVideo/",
+ "/reference/p5/createWriter/",
+ "/reference/p5/cursor/",
+ "/reference/p5/curveDetail/",
+ "/reference/p5/cylinder/",
+ "/reference/p5/day/",
+ "/reference/p5/debugMode/",
+ "/reference/p5/degrees/",
+ "/reference/p5/deltaTime/",
+ "/reference/p5/describe/",
+ "/reference/p5/describeElement/",
+ "/reference/p5/deviceMoved/",
+ "/reference/p5/deviceOrientation/",
+ "/reference/p5/deviceShaken/",
+ "/reference/p5/deviceTurned/",
+ "/reference/p5/directionalLight/",
+ "/reference/p5/disableFriendlyErrors/",
+ "/reference/p5/displayDensity/",
+ "/reference/p5/displayHeight/",
+ "/reference/p5/displayWidth/",
+ "/reference/p5/dist/",
+ "/reference/p5/doubleClicked/",
+ "/reference/p5/draw/",
+ "/reference/p5/drawingContext/",
+ "/reference/p5/ellipse/",
+ "/reference/p5/ellipseMode/",
+ "/reference/p5/ellipsoid/",
+ "/reference/p5/emissiveMaterial/",
+ "/reference/p5/endClip/",
+ "/reference/p5/endContour/",
+ "/reference/p5/endShape/",
+ "/reference/p5/erase/",
+ "/reference/p5/exitPointerLock/",
+ "/reference/p5/exp/",
+ "/reference/p5/fill/",
+ "/reference/p5/filter/",
+ "/reference/p5/filterColor/",
+ "/reference/p5/finalColor/",
+ "/reference/p5/float/",
+ "/reference/p5/floor/",
+ "/reference/p5/focused/",
+ "/reference/p5/fontAscent/",
+ "/reference/p5/fontBounds/",
+ "/reference/p5/fontDescent/",
+ "/reference/p5/fontWidth/",
+ "/reference/p5/for/",
+ "/reference/p5/fract/",
+ "/reference/p5/frameCount/",
+ "/reference/p5/frameRate/",
+ "/reference/p5/freeGeometry/",
+ "/reference/p5/fromAxisAngle/",
+ "/reference/p5/frustum/",
+ "/reference/p5/fullscreen/",
+ "/reference/p5/function/",
+ "/reference/p5/get/",
+ "/reference/p5/getAudioContext/",
+ "/reference/p5/getCameraInputs/",
+ "/reference/p5/getColor/",
+ "/reference/p5/getFinalColor/",
+ "/reference/p5/getItem/",
+ "/reference/p5/getObjectInputs/",
+ "/reference/p5/getPixelInputs/",
+ "/reference/p5/getTargetFrameRate/",
+ "/reference/p5/getTexture/",
+ "/reference/p5/getURL/",
+ "/reference/p5/getURLParams/",
+ "/reference/p5/getURLPath/",
+ "/reference/p5/getWorldInputs/",
+ "/reference/p5/green/",
+ "/reference/p5/gridOutput/",
+ "/reference/p5/height/",
+ "/reference/p5/hex/",
+ "/reference/p5/hour/",
+ "/reference/p5/httpDo/",
+ "/reference/p5/httpGet/",
+ "/reference/p5/httpPost/",
+ "/reference/p5/hue/",
+ "/reference/p5/if/",
+ "/reference/p5/image/",
+ "/reference/p5/imageLight/",
+ "/reference/p5/imageMode/",
+ "/reference/p5/imageShader/",
+ "/reference/p5/int/",
+ "/reference/p5/isLooping/",
+ "/reference/p5/key/",
+ "/reference/p5/keyCode/",
+ "/reference/p5/keyIsDown/",
+ "/reference/p5/keyIsPressed/",
+ "/reference/p5/keyPressed/",
+ "/reference/p5/keyReleased/",
+ "/reference/p5/keyTyped/",
+ "/reference/p5/lerp/",
+ "/reference/p5/lerpColor/",
+ "/reference/p5/let/",
+ "/reference/p5/lightFalloff/",
+ "/reference/p5/lightness/",
+ "/reference/p5/lights/",
+ "/reference/p5/line/",
+ "/reference/p5/linePerspective/",
+ "/reference/p5/loadBlob/",
+ "/reference/p5/loadBytes/",
+ "/reference/p5/loadColorShader/",
+ "/reference/p5/loadFilterShader/",
+ "/reference/p5/loadFont/",
+ "/reference/p5/loadImage/",
+ "/reference/p5/loadJSON/",
+ "/reference/p5/loadMaterialShader/",
+ "/reference/p5/loadModel/",
+ "/reference/p5/loadNormalShader/",
+ "/reference/p5/loadPixels/",
+ "/reference/p5/loadShader/",
+ "/reference/p5/loadSound/",
+ "/reference/p5/loadStrings/",
+ "/reference/p5/loadStrokeShader/",
+ "/reference/p5/loadTable/",
+ "/reference/p5/loadXML/",
+ "/reference/p5/log/",
+ "/reference/p5/loop/",
+ "/reference/p5/mag/",
+ "/reference/p5/map/",
+ "/reference/p5/max/",
+ "/reference/p5/metalness/",
+ "/reference/p5/millis/",
+ "/reference/p5/min/",
+ "/reference/p5/minute/",
+ "/reference/p5/model/",
+ "/reference/p5/month/",
+ "/reference/p5/mouseButton/",
+ "/reference/p5/mouseClicked/",
+ "/reference/p5/mouseDragged/",
+ "/reference/p5/mouseIsPressed/",
+ "/reference/p5/mouseMoved/",
+ "/reference/p5/mousePressed/",
+ "/reference/p5/mouseReleased/",
+ "/reference/p5/mouseWheel/",
+ "/reference/p5/mouseX/",
+ "/reference/p5/mouseY/",
+ "/reference/p5/movedX/",
+ "/reference/p5/movedY/",
+ "/reference/p5/mult/",
+ "/reference/p5/nf/",
+ "/reference/p5/nfc/",
+ "/reference/p5/nfp/",
+ "/reference/p5/nfs/",
+ "/reference/p5/noCanvas/",
+ "/reference/p5/noCursor/",
+ "/reference/p5/noDebugMode/",
+ "/reference/p5/noErase/",
+ "/reference/p5/noFill/",
+ "/reference/p5/noLights/",
+ "/reference/p5/noLoop/",
+ "/reference/p5/noSmooth/",
+ "/reference/p5/noStroke/",
+ "/reference/p5/noTint/",
+ "/reference/p5/noise/",
+ "/reference/p5/noiseDetail/",
+ "/reference/p5/noiseSeed/",
+ "/reference/p5/norm/",
+ "/reference/p5/normal/",
+ "/reference/p5/normalMaterial/",
+ "/reference/p5/objectInputs/",
+ "/reference/p5/orbitControl/",
+ "/reference/p5/ortho/",
+ "/reference/p5/p5.Camera/",
+ "/reference/p5/p5.Color/",
+ "/reference/p5/p5.Element/",
+ "/reference/p5/p5.File/",
+ "/reference/p5/p5.Font/",
+ "/reference/p5/p5.Framebuffer/",
+ "/reference/p5/p5.Geometry/",
+ "/reference/p5/p5.Graphics/",
+ "/reference/p5/p5.Image/",
+ "/reference/p5/p5.MediaElement/",
+ "/reference/p5/p5.PrintWriter/",
+ "/reference/p5/p5.Shader/",
+ "/reference/p5/p5.Table/",
+ "/reference/p5/p5.TableRow/",
+ "/reference/p5/p5.Vector/",
+ "/reference/p5/p5.XML/",
+ "/reference/p5/p5/",
+ "/reference/p5/pAccelerationX/",
+ "/reference/p5/pAccelerationY/",
+ "/reference/p5/pAccelerationZ/",
+ "/reference/p5/pRotationX/",
+ "/reference/p5/pRotationY/",
+ "/reference/p5/pRotationZ/",
+ "/reference/p5/paletteLerp/",
+ "/reference/p5/panorama/",
+ "/reference/p5/perspective/",
+ "/reference/p5/pixelDensity/",
+ "/reference/p5/pixelInputs/",
+ "/reference/p5/pixels/",
+ "/reference/p5/plane/",
+ "/reference/p5/pmouseX/",
+ "/reference/p5/pmouseY/",
+ "/reference/p5/point/",
+ "/reference/p5/pointLight/",
+ "/reference/p5/pop/",
+ "/reference/p5/pow/",
+ "/reference/p5/print/",
+ "/reference/p5/push/",
+ "/reference/p5/pwinMouseX/",
+ "/reference/p5/pwinMouseY/",
+ "/reference/p5/quad/",
+ "/reference/p5/radians/",
+ "/reference/p5/random/",
+ "/reference/p5/randomGaussian/",
+ "/reference/p5/randomSeed/",
+ "/reference/p5/rect/",
+ "/reference/p5/rectMode/",
+ "/reference/p5/red/",
+ "/reference/p5/redraw/",
+ "/reference/p5/registerAddon/",
+ "/reference/p5/remove/",
+ "/reference/p5/removeElements/",
+ "/reference/p5/removeItem/",
+ "/reference/p5/requestPointerLock/",
+ "/reference/p5/resetMatrix/",
+ "/reference/p5/resetShader/",
+ "/reference/p5/resizeCanvas/",
+ "/reference/p5/roll/",
+ "/reference/p5/rotate/",
+ "/reference/p5/rotateBy/",
+ "/reference/p5/rotateX/",
+ "/reference/p5/rotateY/",
+ "/reference/p5/rotateZ/",
+ "/reference/p5/rotationX/",
+ "/reference/p5/rotationY/",
+ "/reference/p5/rotationZ/",
+ "/reference/p5/round/",
+ "/reference/p5/saturation/",
+ "/reference/p5/save/",
+ "/reference/p5/saveCanvas/",
+ "/reference/p5/saveFrames/",
+ "/reference/p5/saveGif/",
+ "/reference/p5/saveJSON/",
+ "/reference/p5/saveObj/",
+ "/reference/p5/saveStl/",
+ "/reference/p5/saveStrings/",
+ "/reference/p5/saveTable/",
+ "/reference/p5/scale/",
+ "/reference/p5/screenToWorld/",
+ "/reference/p5/second/",
+ "/reference/p5/select/",
+ "/reference/p5/selectAll/",
+ "/reference/p5/set/",
+ "/reference/p5/setAttributes/",
+ "/reference/p5/setAudioContext/",
+ "/reference/p5/setCamera/",
+ "/reference/p5/setContent/",
+ "/reference/p5/setMoveThreshold/",
+ "/reference/p5/setShakeThreshold/",
+ "/reference/p5/setup/",
+ "/reference/p5/shader/",
+ "/reference/p5/shearX/",
+ "/reference/p5/shearY/",
+ "/reference/p5/shininess/",
+ "/reference/p5/shuffle/",
+ "/reference/p5/sin/",
+ "/reference/p5/smooth/",
+ "/reference/p5/smoothstep/",
+ "/reference/p5/specularColor/",
+ "/reference/p5/specularMaterial/",
+ "/reference/p5/sphere/",
+ "/reference/p5/spline/",
+ "/reference/p5/splinePoint/",
+ "/reference/p5/splineProperties/",
+ "/reference/p5/splineProperty/",
+ "/reference/p5/splineTangent/",
+ "/reference/p5/splineVertex/",
+ "/reference/p5/splitTokens/",
+ "/reference/p5/spotLight/",
+ "/reference/p5/sq/",
+ "/reference/p5/sqrt/",
+ "/reference/p5/square/",
+ "/reference/p5/storeItem/",
+ "/reference/p5/str/",
+ "/reference/p5/stroke/",
+ "/reference/p5/strokeCap/",
+ "/reference/p5/strokeJoin/",
+ "/reference/p5/strokeMode/",
+ "/reference/p5/strokeShader/",
+ "/reference/p5/strokeWeight/",
+ "/reference/p5/tan/",
+ "/reference/p5/text/",
+ "/reference/p5/textAlign/",
+ "/reference/p5/textAscent/",
+ "/reference/p5/textBounds/",
+ "/reference/p5/textDescent/",
+ "/reference/p5/textDirection/",
+ "/reference/p5/textFont/",
+ "/reference/p5/textLeading/",
+ "/reference/p5/textOutput/",
+ "/reference/p5/textProperties/",
+ "/reference/p5/textProperty/",
+ "/reference/p5/textSize/",
+ "/reference/p5/textStyle/",
+ "/reference/p5/textWeight/",
+ "/reference/p5/textWidth/",
+ "/reference/p5/textWrap/",
+ "/reference/p5/texture/",
+ "/reference/p5/textureMode/",
+ "/reference/p5/textureWrap/",
+ "/reference/p5/tint/",
+ "/reference/p5/torus/",
+ "/reference/p5/touches/",
+ "/reference/p5/translate/",
+ "/reference/p5/triangle/",
+ "/reference/p5/turnAxis/",
+ "/reference/p5/types/Array/",
+ "/reference/p5/types/Boolean/",
+ "/reference/p5/types/Number/",
+ "/reference/p5/types/Object/",
+ "/reference/p5/types/String/",
+ "/reference/p5/unchar/",
+ "/reference/p5/unhex/",
+ "/reference/p5/updatePixels/",
+ "/reference/p5/userStartAudio/",
+ "/reference/p5/userStopAudio/",
+ "/reference/p5/vertex/",
+ "/reference/p5/vertexProperty/",
+ "/reference/p5/webglVersion/",
+ "/reference/p5/while/",
+ "/reference/p5/width/",
+ "/reference/p5/winMouseX/",
+ "/reference/p5/winMouseY/",
+ "/reference/p5/windowHeight/",
+ "/reference/p5/windowResized/",
+ "/reference/p5/windowWidth/",
+ "/reference/p5/worldInputs/",
+ "/reference/p5/worldToScreen/",
+ "/reference/p5/write/",
+ "/reference/p5/year/",
+];
diff --git a/playwright-tests/screenshot-proposed-colour-changes.spec.ts.disabled b/playwright-tests/screenshot-proposed-colour-changes.spec.ts.disabled
new file mode 100644
index 0000000000..19f5caa266
--- /dev/null
+++ b/playwright-tests/screenshot-proposed-colour-changes.spec.ts.disabled
@@ -0,0 +1,108 @@
+// Note: how to record test
+// 1. in vscode, `View: Show Testing`
+// 2. from the Playwright sidebar choose "Record New" (or "Record at cursor")
+// 3. wait for it to open GUI tool
+// 4. click around
+// 5. stop recording in top toolbar of test tool (not vscode)
+// 6. script will be written into new editor tab or at cursor (depending on choice in step 2)
+
+
+import { test, type Page } from "@playwright/test";
+
+test("screenshot alt colours", async ({ page }) => {
+
+ await page.goto('https://editor.p5js.org/');
+
+ //accept /dismiss cookie banner
+ await page.getByRole('button', { name: 'Allow Essential' }).click();
+
+ //switch on dark mode once for editor
+ //set dark mode
+ await page.getByRole('button', { name: 'Open Preferences' }).click();
+ await page.getByText('Dark').click();
+ await page.getByRole('button', { name: 'Close Settings overlay' }).click();
+
+ //1. do for settings
+ await page.getByRole('button', { name: 'p5.js' }).click();
+
+ await runAllStyleTrials(page, "in-settings");
+
+ await page.getByRole('button', { name: 'Close Settings overlay' }).click();
+
+ //2. do for about page
+ await page.getByRole('menuitem', { name: 'Help' }).click();
+ await page.getByRole('menuitem', { name: 'About' }).click();
+ await page.getByRole('link', { name: 'Report a Bug' }).scrollIntoViewIfNeeded();
+ //alternative:
+ // await page.evaluate(() => {
+ // window.scrollTo(0, 500); //some arbitrary y position
+ // })
+
+ await runAllStyleTrials(page, "in-about");
+
+ //3. do for file dialog
+ await page.getByRole('link', { name: 'Back to Editor' }).click();
+ await page.getByRole('menuitem', { name: 'File' }).click();
+ await page.getByRole('menuitem', { name: 'Examples' }).click();
+
+ await runAllStyleTrials(page, "in-file-list");
+
+
+});
+
+async function runAllStyleTrials(page: Page, locationLabel: string) {
+ const changesets = [
+ //Note this automation doesn't tidy up (doesn't reload) after each change,
+ // so for now these changesets MUST all completely replace each other.
+ { name: "2-middle-light", style: { color: "#a6a6a6" } },
+ { name: "2-medium-light", style: { color: "#d9d9d9" } },
+ { name: "2-contrast-pink", style: { color: "#ffa9d9" } },
+ { name: "3-p5-pink-ish", style: { color: "#FF6691" } },
+ // { name: "1-changed-bg-and-link", style: { background: "#000000", color: "#eb285f" } },
+ ]
+
+
+ for (const changeset of changesets) {
+ //not needed
+ await page.waitForTimeout(500);
+ //TODO: how much to scroll?
+ // perhaps until the first relevant is in middle of viewport, ideally?
+ // await scrollToBottom(page);
+
+ await applyThemeOverride(page, "a", changeset.style);
+
+ await page.screenshot({ path: `${locationLabel}-${changeset.name}.png` });
+ }
+}
+
+
+
+
+
+/**
+ * Injects a temporary CSS override into the page.
+ */
+async function applyThemeOverride(page: Page, selector: string, style: { color: string }): Promise {
+
+ await page.addStyleTag({
+
+ content: `
+ ${selector} {
+ color: ${style.color} !important;
+ }
+`
+ });
+
+ const cssForBackgroundOverridesUNUSED = `
+.preference {background: #000000}
+
+tr.sketches-table__row {
+ background: #000000 !important;
+}
+
+.RootPage-sc-1nw49or-0 {
+ background:#000000;
+}
+`;
+
+}
diff --git a/playwright.config.ts b/playwright.config.ts
new file mode 100644
index 0000000000..af52468b2b
--- /dev/null
+++ b/playwright.config.ts
@@ -0,0 +1,79 @@
+import { defineConfig, devices } from '@playwright/test';
+
+/**
+ * Read environment variables from file.
+ * https://github.com/motdotla/dotenv
+ */
+// import dotenv from 'dotenv';
+// import path from 'path';
+// dotenv.config({ path: path.resolve(__dirname, '.env') });
+
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+export default defineConfig({
+ testDir: './playwright-tests',
+ /* Run tests in files in parallel */
+ fullyParallel: true,
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
+ forbidOnly: !!process.env.CI,
+ /* Retry on CI only */
+ retries: process.env.CI ? 2 : 0,
+ /* Opt out of parallel tests on CI. */
+ workers: process.env.CI ? 1 : undefined,
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
+ reporter: 'html',
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
+ use: {
+ /* Base URL to use in actions like `await page.goto('')`. */
+ // baseURL: 'http://localhost:3000',
+
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
+ trace: 'on-first-retry',
+ },
+
+ /* Configure projects for major browsers */
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+
+ {
+ name: 'firefox',
+ use: { ...devices['Desktop Firefox'] },
+ },
+
+ {
+ name: 'webkit',
+ use: { ...devices['Desktop Safari'] },
+ },
+
+ /* Test against mobile viewports. */
+ // {
+ // name: 'Mobile Chrome',
+ // use: { ...devices['Pixel 5'] },
+ // },
+ // {
+ // name: 'Mobile Safari',
+ // use: { ...devices['iPhone 12'] },
+ // },
+
+ /* Test against branded browsers. */
+ // {
+ // name: 'Microsoft Edge',
+ // use: { ...devices['Desktop Edge'], channel: 'msedge' },
+ // },
+ // {
+ // name: 'Google Chrome',
+ // use: { ...devices['Desktop Chrome'], channel: 'chrome' },
+ // },
+ ],
+
+ /* Run your local dev server before starting the tests */
+ // webServer: {
+ // command: 'npm run start',
+ // url: 'http://localhost:3000',
+ // reuseExistingServer: !process.env.CI,
+ // },
+});