diff --git a/.github/workflows/cf_test_full.yml b/.github/workflows/cf_test_full.yml index 17dc01260..a1ee30683 100644 --- a/.github/workflows/cf_test_full.yml +++ b/.github/workflows/cf_test_full.yml @@ -12,6 +12,10 @@ on: - BISO - BRAPI Staging - BRAPI Production + grep: + description: 'Test filter grep' + required: false + type: string jobs: full-tests: @@ -19,6 +23,7 @@ jobs: env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + BROWSER_BINDINGS: ${{ secrets.BROWSER_BINDINGS }} TESTS_SERVER_URL: https://playwright-test-workers.rendering.cfdata.org CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF_ACCESS_CLIENT_SECRET }} CF_ACCESS_CLIENT_ID: ${{ secrets.CF_ACCESS_CLIENT_ID }} @@ -37,7 +42,7 @@ jobs: - name: Run tests run: | cd packages/playwright-cloudflare - npm run test:full -- --reporter=html --project="${{ github.event.inputs.project || 'BISO' }}" + npm run test:full -- --reporter=html --project="${{ github.event.inputs.project || 'BISO' }}" ${{ github.event.inputs.grep && format('--grep="{0}"', github.event.inputs.grep) }} - name: Upload HTML report uses: actions/upload-artifact@v4 @@ -48,7 +53,7 @@ jobs: retention-days: 14 - name: Deploy to Cloudflare Pages - if: ${{ !cancelled() }} + if: ${{ !cancelled() && !github.event.inputs.grep }} run: | cd packages/playwright-cloudflare/tests npx wrangler pages deploy ./playwright-report --project-name playwright-full-test-report diff --git a/README.md b/README.md index 624a5a774..fadc99707 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,9 @@ export default { ### Trace ```js +import fs from "fs"; + import { launch } from "@cloudflare/playwright"; -import fs from "@cloudflare/playwright/fs"; export default { async fetch(request, env): Promise { @@ -86,9 +87,10 @@ export default { // ... do something, screenshot for example - await page.context().tracing.stop({ path: "trace.zip" }); + // For now, fs only supports writing into /tmp + await page.context().tracing.stop({ path: "/tmp/trace.zip" }); await browser.close(); - const file = await fs.promises.readFile("trace.zip"); + const file = await fs.promises.readFile("/tmp/trace.zip"); return new Response(file, { status: 200, diff --git a/packages/playwright-cloudflare/bundles/fs/package-lock.json b/packages/playwright-cloudflare/bundles/fs/package-lock.json deleted file mode 100644 index c42780d3b..000000000 --- a/packages/playwright-cloudflare/bundles/fs/package-lock.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "name": "fs-bundle", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "fs-bundle", - "version": "0.0.1", - "dependencies": { - "memfs": "^4.17.0" - } - }, - "node_modules/@jsonjoy.com/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/json-pack": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.1.tgz", - "integrity": "sha512-osjeBqMJ2lb/j/M8NCPjs1ylqWIcTRTycIhVB5pt6LgzgeRSb0YRZ7j9RfA8wIUrsr/medIuhVyonXRZWLyfdw==", - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/base64": "^1.1.1", - "@jsonjoy.com/util": "^1.1.2", - "hyperdyperid": "^1.2.0", - "thingies": "^1.20.0" - }, - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/util": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", - "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/hyperdyperid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", - "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", - "license": "MIT", - "engines": { - "node": ">=10.18" - } - }, - "node_modules/memfs": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.0.tgz", - "integrity": "sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg==", - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/json-pack": "^1.0.3", - "@jsonjoy.com/util": "^1.3.0", - "tree-dump": "^1.0.1", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">= 4.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - } - }, - "node_modules/thingies": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", - "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", - "license": "Unlicense", - "engines": { - "node": ">=10.18" - }, - "peerDependencies": { - "tslib": "^2" - } - }, - "node_modules/tree-dump": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", - "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - } - } -} diff --git a/packages/playwright-cloudflare/bundles/fs/package.json b/packages/playwright-cloudflare/bundles/fs/package.json deleted file mode 100644 index 542beac94..000000000 --- a/packages/playwright-cloudflare/bundles/fs/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "fs-bundle", - "version": "0.0.1", - "private": true, - "dependencies": { - "memfs": "^4.17.0" - } -} diff --git a/packages/playwright-cloudflare/bundles/fs/src/fs.ts b/packages/playwright-cloudflare/bundles/fs/src/fs.ts deleted file mode 100644 index ccabd0b7b..000000000 --- a/packages/playwright-cloudflare/bundles/fs/src/fs.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { memfs } from 'memfs'; - -const { fs } = memfs({ '/tmp': null }); - -export const { - appendFile, - appendFileSync, - access, - accessSync, - chown, - chownSync, - chmod, - chmodSync, - close, - closeSync, - copyFile, - copyFileSync, - cp, - cpSync, - createReadStream, - createWriteStream, - exists, - existsSync, - fchown, - fchownSync, - fchmod, - fchmodSync, - fdatasync, - fdatasyncSync, - fstat, - fstatSync, - fsync, - fsyncSync, - ftruncate, - ftruncateSync, - futimes, - futimesSync, - lchown, - lchownSync, - lchmod, - lchmodSync, - link, - linkSync, - lstat, - lstatSync, - lutimes, - lutimesSync, - mkdir, - mkdirSync, - mkdtemp, - mkdtempSync, - open, - openSync, - opendir, - opendirSync, - readdir, - readdirSync, - read, - readSync, - readv, - readvSync, - readFile, - readFileSync, - readlink, - readlinkSync, - realpath, - realpathSync, - rename, - renameSync, - rm, - rmSync, - rmdir, - rmdirSync, - stat, - statfs, - statSync, - statfsSync, - symlink, - symlinkSync, - truncate, - truncateSync, - unwatchFile, - unlink, - unlinkSync, - utimes, - utimesSync, - watch, - watchFile, - writeFile, - writeFileSync, - write, - writeSync, - writev, - writevSync, - Dirent, - Stats, - ReadStream, - WriteStream, - constants, - promises, -} = fs; - -export default fs; diff --git a/packages/playwright-cloudflare/examples/todomvc/README.md b/packages/playwright-cloudflare/examples/todomvc/README.md index ff54e7920..1d8d795ba 100644 --- a/packages/playwright-cloudflare/examples/todomvc/README.md +++ b/packages/playwright-cloudflare/examples/todomvc/README.md @@ -37,14 +37,15 @@ https://.workers.dev ``` Returns a screenshot of the TodoMVC app with default todo items. -### Custom Todo Items +### Generate Trace + +To generate a trace and open it in [Playwright Trace Viewer](https://trace.playwright.dev/), use the following URL: + ``` -https://.workers.dev?todo=first&todo=second +https://trace.playwright.dev/?trace=https://.workers.dev ``` -Returns a screenshot with your custom todo items. -### Generate Trace +To download the trace, use the following URL: ``` https://.workers.dev?trace ``` -Returns a Playwright trace file that can be viewed in [Playwright Trace Viewer](https://playwright.dev/docs/trace-viewer). diff --git a/packages/playwright-cloudflare/examples/todomvc/package-lock.json b/packages/playwright-cloudflare/examples/todomvc/package-lock.json index 459d90e0a..9d33d2495 100644 --- a/packages/playwright-cloudflare/examples/todomvc/package-lock.json +++ b/packages/playwright-cloudflare/examples/todomvc/package-lock.json @@ -9,11 +9,20 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@cloudflare/playwright": "^0.0.11" + "@cloudflare/playwright": "file:../.." }, "devDependencies": { "typescript": "^5.8.3", - "wrangler": "^4.32.0" + "wrangler": "^4.38.0" + } + }, + "../..": { + "name": "@cloudflare/playwright", + "version": "0.0.1-next", + "license": "Apache-2.0", + "devDependencies": { + "pkg-pr-new": "^0.0.59", + "vite": "^6.1.0" } }, "node_modules/@cloudflare/kv-asset-handler": { @@ -30,20 +39,18 @@ } }, "node_modules/@cloudflare/playwright": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/@cloudflare/playwright/-/playwright-0.0.11.tgz", - "integrity": "sha512-5meAHlST5K3MKYA7TyL6Ns1XQhYB615qYZxDSn0UnSX14MFgRe+pqlq482dXbgMBappnvvsjZl+Ea3/FQcdbvQ==", - "license": "Apache-2.0" + "resolved": "../..", + "link": true }, "node_modules/@cloudflare/unenv-preset": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.6.2.tgz", - "integrity": "sha512-C7/tW7Qy+wGOCmHXu7xpP1TF3uIhRoi7zVY7dmu/SOSGjPilK+lSQ2lIRILulZsT467ZJNlI0jBxMbd8LzkGRg==", + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.7.4.tgz", + "integrity": "sha512-KIjbu/Dt50zseJIoOOK5y4eYpSojD9+xxkePYVK1Rg9k/p/st4YyMtz1Clju/zrenJHrOH+AAcjNArOPMwH4Bw==", "dev": true, "license": "MIT OR Apache-2.0", "peerDependencies": { - "unenv": "2.0.0-rc.19", - "workerd": "^1.20250802.0" + "unenv": "2.0.0-rc.21", + "workerd": "^1.20250912.0" }, "peerDependenciesMeta": { "workerd": { @@ -52,9 +59,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250816.0.tgz", - "integrity": "sha512-yN1Rga4ufTdrJPCP4gEqfB47i1lWi3teY5IoeQbUuKnjnCtm4pZvXur526JzCmaw60Jx+AEWf5tizdwRd5hHBQ==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250917.0.tgz", + "integrity": "sha512-0kL/kFnKUSycoo7b3PgM0nRyZ+1MGQAKaXtE6a2+SAeUkZ2FLnuFWmASi0s4rlWGsf/rlTw4AwXROePir9dUcQ==", "cpu": [ "x64" ], @@ -69,9 +76,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250816.0.tgz", - "integrity": "sha512-WyKPMQhbU+TTf4uDz3SA7ZObspg7WzyJMv/7J4grSddpdx2A4Y4SfPu3wsZleAOIMOAEVi0A1sYDhdltKM7Mxg==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250917.0.tgz", + "integrity": "sha512-3/N1QmEJsC8Byxt1SGgVp5o0r+eKjuUEMbIL2yzLk/jrMdErPXy/DGf/tXZoACU68a/gMEbbT1itkYrm85iQHg==", "cpu": [ "arm64" ], @@ -86,9 +93,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250816.0.tgz", - "integrity": "sha512-NWHOuFnVBaPRhLHw8kjPO9GJmc2P/CTYbnNlNm0EThyi57o/oDx0ldWLJqEHlrdEPOw7zEVGBqM/6M+V9agC6w==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250917.0.tgz", + "integrity": "sha512-E7sEow7CErbWY3olMmlbj6iss9r7Xb2uMyc+MKzYC9/J6yFlJd/dNHvjey9QIdxzbkC9qGe90a+KxQrjs+fspA==", "cpu": [ "x64" ], @@ -103,9 +110,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250816.0.tgz", - "integrity": "sha512-FR+/yhaWs7FhfC3GKsM3+usQVrGEweJ9qyh7p+R6HNwnobgKr/h5ATWvJ4obGJF6ZHHodgSe+gOSYR7fkJ1xAQ==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250917.0.tgz", + "integrity": "sha512-roOnRjxut2FUxo6HA9spbfs32naXAsnSQqsgku3iq6BYKv1QqGiFoY5bReK72N5uxmhxo7+RiTo8ZEkxA/vMIQ==", "cpu": [ "arm64" ], @@ -120,9 +127,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250816.0.tgz", - "integrity": "sha512-0lqClj2UMhFa8tCBiiX7Zhd5Bjp0V+X8oNBG6V6WsR9p9/HlIHAGgwRAM7aYkyG+8KC8xlbC89O2AXUXLpHx0g==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250917.0.tgz", + "integrity": "sha512-gslh6Ou9+kshHjR1BJX47OsbPw3/cZCvGDompvaW/URCgr7aMzljbgmBb7p0uhwGy1qCXcIt31St6pd3IEcLng==", "cpu": [ "x64" ], @@ -150,9 +157,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", - "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", "dev": true, "license": "MIT", "optional": true, @@ -1023,9 +1030,9 @@ "license": "MIT" }, "node_modules/@sindresorhus/is": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.2.tgz", - "integrity": "sha512-d9xRovfKNz1SKieM0qJdO+PQonjnnIfSNWfHYnBSJ9hkjm0ZPw6HlxscDXYstp3z+7V2GOFHc+J0CYrYTjqCJw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.0.tgz", + "integrity": "sha512-7F/yz2IphV39hiS2zB4QYVkivrptHHh0K8qJJd9HhuWSdvf8AN7NpebW3CcDZDBQsUPMoDKWsY2WWgW7bqOcfA==", "dev": true, "license": "MIT", "engines": { @@ -1135,9 +1142,9 @@ "license": "MIT" }, "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz", + "integrity": "sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1238,9 +1245,9 @@ "license": "BSD-2-Clause" }, "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", "dev": true, "license": "MIT" }, @@ -1268,9 +1275,9 @@ } }, "node_modules/miniflare": { - "version": "4.20250816.1", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250816.1.tgz", - "integrity": "sha512-2X8yMy5wWw0dF1pNU4kztzZgp0jWv2KMqAOOb2FeQ/b11yck4aczmYHi7UYD3uyOgtj8WFhwG/KdRWAaATTtRA==", + "version": "4.20250917.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250917.0.tgz", + "integrity": "sha512-A7kYEc/Y6ohiiTji4W/qGJj3aJNc/9IMj/6wLy2phD/iMjcoY8t35654gR5mHbMx0AgUolDdr3HOsHB0cYBf+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1281,8 +1288,8 @@ "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", - "undici": "^7.10.0", - "workerd": "1.20250816.0", + "undici": "7.14.0", + "workerd": "1.20250917.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" @@ -1369,9 +1376,9 @@ } }, "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", "dev": true, "license": "MIT", "dependencies": { @@ -1390,9 +1397,9 @@ } }, "node_modules/supports-color": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.0.tgz", - "integrity": "sha512-5eG9FQjEjDbAlI5+kdpdyPIBMRH4GfTVDGREVupaZHmVoppknhM29b/S9BkQz7cathp85BVgRi/As3Siln7e0Q==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", "dev": true, "license": "MIT", "engines": { @@ -1432,9 +1439,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.15.0.tgz", - "integrity": "sha512-7oZJCPvvMvTd0OlqWsIxTuItTpJBpU1tcbVl24FMn3xt3+VSunwUasmfPJRE57oNO1KsZ4PgA1xTdAX4hq8NyQ==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.14.0.tgz", + "integrity": "sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==", "dev": true, "license": "MIT", "engines": { @@ -1442,9 +1449,9 @@ } }, "node_modules/unenv": { - "version": "2.0.0-rc.19", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.19.tgz", - "integrity": "sha512-t/OMHBNAkknVCI7bVB9OWjUUAwhVv9vsPIAGnNUxnu3FxPQN11rjh0sksLMzc3g7IlTgvHmOTl4JM7JHpcv5wA==", + "version": "2.0.0-rc.21", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.21.tgz", + "integrity": "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A==", "dev": true, "license": "MIT", "dependencies": { @@ -1456,9 +1463,9 @@ } }, "node_modules/workerd": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250816.0.tgz", - "integrity": "sha512-5gIvHPE/3QVlQR1Sc1NdBkWmqWj/TSgIbY/f/qs9lhiLBw/Da+HbNBTVYGjvwYqEb3NQ+XQM4gAm5b2+JJaUJg==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250917.0.tgz", + "integrity": "sha512-0D+wWaccyYQb2Zx2DZDC77YDn9kOpkpGMCgyKgIHilghut5hBQ/adUIEseS4iuIZxBPeFSn6zFtICP0SxZ3z0g==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -1469,28 +1476,28 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20250816.0", - "@cloudflare/workerd-darwin-arm64": "1.20250816.0", - "@cloudflare/workerd-linux-64": "1.20250816.0", - "@cloudflare/workerd-linux-arm64": "1.20250816.0", - "@cloudflare/workerd-windows-64": "1.20250816.0" + "@cloudflare/workerd-darwin-64": "1.20250917.0", + "@cloudflare/workerd-darwin-arm64": "1.20250917.0", + "@cloudflare/workerd-linux-64": "1.20250917.0", + "@cloudflare/workerd-linux-arm64": "1.20250917.0", + "@cloudflare/workerd-windows-64": "1.20250917.0" } }, "node_modules/wrangler": { - "version": "4.32.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.32.0.tgz", - "integrity": "sha512-q7TRSavBW3Eg3pp4rxqKJwSK+u/ieFOBdNvUsq1P1EMmyj3//tN/iXDokFak+dkW0vDYjsVG3PfOfHxU92OS6w==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.38.0.tgz", + "integrity": "sha512-ITL4VZ4KWs8LMDEttDTrAKLktwtv1NxHBd5QIqHOczvcjnAQr+GQoE6XYQws+w8jlOjDV7KyvbFqAdyRh5om3g==", "dev": true, "license": "MIT OR Apache-2.0", "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", - "@cloudflare/unenv-preset": "2.6.2", + "@cloudflare/unenv-preset": "2.7.4", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", - "miniflare": "4.20250816.1", + "miniflare": "4.20250917.0", "path-to-regexp": "6.3.0", - "unenv": "2.0.0-rc.19", - "workerd": "1.20250816.0" + "unenv": "2.0.0-rc.21", + "workerd": "1.20250917.0" }, "bin": { "wrangler": "bin/wrangler.js", @@ -1503,7 +1510,7 @@ "fsevents": "~2.3.2" }, "peerDependencies": { - "@cloudflare/workers-types": "^4.20250816.0" + "@cloudflare/workers-types": "^4.20250917.0" }, "peerDependenciesMeta": { "@cloudflare/workers-types": { diff --git a/packages/playwright-cloudflare/examples/todomvc/package.json b/packages/playwright-cloudflare/examples/todomvc/package.json index 5afddda3e..6f9671d1e 100644 --- a/packages/playwright-cloudflare/examples/todomvc/package.json +++ b/packages/playwright-cloudflare/examples/todomvc/package.json @@ -11,9 +11,9 @@ }, "devDependencies": { "typescript": "^5.8.3", - "wrangler": "^4.32.0" + "wrangler": "^4.38.0" }, "dependencies": { - "@cloudflare/playwright": "^0.0.11" + "@cloudflare/playwright": "file:../.." } } diff --git a/packages/playwright-cloudflare/examples/todomvc/src/index.ts b/packages/playwright-cloudflare/examples/todomvc/src/index.ts index 33b954e7b..aa5eb7709 100644 --- a/packages/playwright-cloudflare/examples/todomvc/src/index.ts +++ b/packages/playwright-cloudflare/examples/todomvc/src/index.ts @@ -1,16 +1,32 @@ +import fs from 'fs'; + import { launch } from '@cloudflare/playwright'; import { expect } from '@cloudflare/playwright/test'; -import fs from '@cloudflare/playwright/fs'; + +const CORS_HEADERS = { + 'Access-Control-Allow-Origin': 'https://trace.playwright.dev', + 'Access-Control-Allow-Methods': 'GET', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', +}; export default { async fetch(request: Request, env: Env) { - const { searchParams } = new URL(request.url); - const todos = searchParams.getAll('todo'); - const trace = searchParams.has('trace'); + const url = new URL(request.url); + + if (url.pathname !== '/') { + return new Response(null, { status: 404 }); + } + if (request.method === 'OPTIONS') { + return new Response(null, { headers: CORS_HEADERS }); + } + + const trace = url.searchParams.has('trace') || request.headers.get('referer') === 'https://trace.playwright.dev/'; + const todos = url.searchParams.getAll('todo'); + const browser = await launch(env.MYBROWSER); const page = await browser.newPage(); - + if (trace) await page.context().tracing.start({ screenshots: true, snapshots: true }); @@ -35,21 +51,24 @@ export default { )); if (trace) { - await page.context().tracing.stop({ path: 'trace.zip' }); + // we must write the trace to /tmp as it is the only directory + // that is writable in the worker + await page.context().tracing.stop({ path: '/tmp/trace.zip' }); await browser.close(); - const file = await fs.promises.readFile('trace.zip'); + const file = await fs.promises.readFile('/tmp/trace.zip'); - return new Response(file, { + return new Response(new Uint8Array(file), { status: 200, headers: { 'Content-Type': 'application/zip', + ...CORS_HEADERS, }, }); } else { const img = await page.screenshot(); await browser.close(); - return new Response(img, { + return new Response(new Uint8Array(img), { headers: { 'Content-Type': 'image/png', }, diff --git a/packages/playwright-cloudflare/examples/todomvc/wrangler.toml b/packages/playwright-cloudflare/examples/todomvc/wrangler.toml index 84e5ba118..cb5e080ac 100644 --- a/packages/playwright-cloudflare/examples/todomvc/wrangler.toml +++ b/packages/playwright-cloudflare/examples/todomvc/wrangler.toml @@ -2,8 +2,11 @@ name = "cloudflare-playwright-example" main = "src/index.ts" workers_dev = true compatibility_flags = ["nodejs_compat"] -compatibility_date = "2025-03-05" +compatibility_date = "2025-09-17" upload_source_maps = true [browser] binding = "MYBROWSER" + +[observability] +enabled = true diff --git a/packages/playwright-cloudflare/fs.d.ts b/packages/playwright-cloudflare/fs.d.ts deleted file mode 100644 index 7c93d5ee0..000000000 --- a/packages/playwright-cloudflare/fs.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from 'fs'; - -import fs from 'fs'; - -export default fs; diff --git a/packages/playwright-cloudflare/index.d.ts b/packages/playwright-cloudflare/index.d.ts index d92dacf4a..949e953fc 100644 --- a/packages/playwright-cloudflare/index.d.ts +++ b/packages/playwright-cloudflare/index.d.ts @@ -8,7 +8,7 @@ export * from './types/types'; declare module './types/types' { interface Browser { /** - * Get the BISO session ID associated with this browser + * Get the Browser Rendering session ID associated with this browser * * @public */ @@ -41,7 +41,7 @@ export interface ActiveSession { // connection info, if present means there's a connection established // from a worker to that session connectionId?: string; - connectionStartTime?: string; + connectionStartTime?: number; } /** @@ -88,17 +88,24 @@ export interface WorkersLaunchOptions { keep_alive?: number; // milliseconds to keep browser alive even if it has no activity (from 10_000ms to 600_000ms, default is 60_000) } +/** + * @public + */ +export interface WorkersConnectOptions { + sessionId: string; // session ID to connect to +} + // Extracts the keys whose values match a specified type `ValueType` -type KeysByValue = { +type KeysByValueType = { [K in keyof T]: T[K] extends ValueType ? K : never; }[keyof T]; -export type BrowserBindingKey = KeysByValue; +export type BrowserBindingKey = KeysByValueType; -export function endpointURLString(binding: BrowserWorker | BrowserBindingKey, options?: { sessionId?: string, persistent?: boolean }): string; +export function endpointURLString(binding: BrowserWorker | BrowserBindingKey, options?: WorkersLaunchOptions | WorkersConnectOptions): string; export function connect(endpoint: string | URL): Promise; -export function connect(endpoint: BrowserWorker, sessionIdOrOptions: string | { sessionId: string, persistent?: boolean }): Promise; +export function connect(endpoint: BrowserWorker, sessionIdOrOptions: string | WorkersConnectOptions): Promise; export function launch(endpoint: BrowserEndpoint, options?: WorkersLaunchOptions): Promise; diff --git a/packages/playwright-cloudflare/package.json b/packages/playwright-cloudflare/package.json index f7a2030e1..08bd2b10d 100644 --- a/packages/playwright-cloudflare/package.json +++ b/packages/playwright-cloudflare/package.json @@ -7,27 +7,15 @@ "exports": { ".": { "types": "./index.d.ts", - "import": "./lib/esm/index.js", - "require": "./lib/cjs/index.js", - "default": "./lib/esm/index.js" + "default": "./lib/index.js" }, "./test": { "types": "./test.d.ts", - "import": "./lib/esm/test.js", - "require": "./lib/cjs/test.js", - "default": "./lib/esm/test.js" + "default": "./lib/test.js" }, "./internal": { "types": "./internal.d.ts", - "import": "./lib/esm/internal.js", - "require": "./lib/cjs/internal.js", - "default": "./lib/esm/internal.js" - }, - "./fs": { - "types": "./fs.d.ts", - "import": "./lib/esm/bundles/fs.js", - "require": "./lib/cjs/bundles/fs.js", - "default": "./lib/esm/bundles/fs.js" + "default": "./lib/internal.js" } }, "scripts": { @@ -35,9 +23,8 @@ "ci:core:bundles:utils": "cd ../playwright-core/bundles/utils && npm ci", "ci:core:bundles:zip": "cd ../playwright-core/bundles/zip && npm ci", "ci:test:bundles:expect": "cd ../playwright/bundles/expect && npm ci", - "ci:bundles:fs": "cd ./bundles/fs && npm ci", "ci:bundles:pngjs": "cd ./bundles/pngjs && npm ci", - "ci:bundles": "npm run ci:core:bundles:utils && npm run ci:core:bundles:zip && npm run ci:test:bundles:expect && npm run ci:bundles:fs && npm run ci:bundles:pngjs", + "ci:bundles": "npm run ci:core:bundles:utils && npm run ci:core:bundles:zip && npm run ci:test:bundles:expect && npm run ci:bundles:pngjs", "ci:tests": "cd tests && npm ci", "build:bundles": "node utils/build_bundles.js", "build:types": "node utils/copy_types.js", @@ -46,7 +33,7 @@ "test:generate:proxy": "node ./utils/generate_proxy_tests.js", "test:generate:wrangler": "node ./utils/generate_test_wrangler.js", "test:deploy": "npm run test:generate:wrangler && npm run test:generate:worker && npm run ci:tests && cd tests && npx wrangler deploy -c wrangler-test.toml", - "test:dev": "npm run test:generate:worker && cd tests && npx wrangler dev --remote", + "test:dev": "npm run test:generate:worker && cd tests && npx wrangler dev", "test:full": "npm run test:generate:proxy && cd tests && npx playwright test", "test:smoke": "npm run test:generate:proxy && cd tests && npx playwright test --grep \"@smoke\"" }, diff --git a/packages/playwright-cloudflare/src/cloudflare/wrapClientApis.ts b/packages/playwright-cloudflare/src/cloudflare/wrapClientApis.ts index e8b9c7e89..53e9db9fb 100644 --- a/packages/playwright-cloudflare/src/cloudflare/wrapClientApis.ts +++ b/packages/playwright-cloudflare/src/cloudflare/wrapClientApis.ts @@ -24,7 +24,7 @@ import { Browser, BrowserContext, BrowserType, - // Clock, + Clock, ConsoleMessage, Coverage, Dialog, @@ -43,7 +43,7 @@ import { JSHandle, Route, WebSocket, - // WebSocketRoute, + WebSocketRoute, APIRequest, APIRequestContext, APIResponse, @@ -69,7 +69,7 @@ type ApiTypeMap = { 'browser': Browser, 'browserContext': BrowserContext, 'browserType': BrowserType, - // 'clock': Clock, + 'clock': Clock, 'consoleMessage': ConsoleMessage, 'coverage': Coverage, 'dialog': Dialog, @@ -88,7 +88,7 @@ type ApiTypeMap = { 'jSHandle': JSHandle, 'route': Route, 'webSocket': WebSocket, - // 'webSocketRoute': WebSocketRoute, + 'webSocketRoute': WebSocketRoute, 'request': APIRequest, 'requestContext': APIRequestContext, 'response': APIResponse, @@ -141,7 +141,7 @@ const apis: { [ApiK in keyof ApiTypeMap]: [ApiTypeMap[ApiK], { [K in KeysOfAsync routeWebSocket: true }], browserType: [BrowserType.prototype, { launch: true, launchServer: true, launchPersistentContext: true, connect: true, connectOverCDP: true }], - // clock: [Clock.prototype, { install: true, fastForward: true, pauseAt: true, resume: true, runFor: true, setFixedTime: true, setSystemTime: true }], + clock: [Clock.prototype, { install: true, fastForward: true, pauseAt: true, resume: true, runFor: true, setFixedTime: true, setSystemTime: true }], consoleMessage: [ConsoleMessage.prototype, {}], coverage: [Coverage.prototype, { startCSSCoverage: true, stopCSSCoverage: true, startJSCoverage: true, stopJSCoverage: true }], dialog: [Dialog.prototype, { accept: true, dismiss: true }], @@ -298,7 +298,7 @@ const apis: { [ApiK in keyof ApiTypeMap]: [ApiTypeMap[ApiK], { [K in KeysOfAsync jSHandle: [JSHandle.prototype, { evaluate: true, evaluateHandle: true, getProperty: true, jsonValue: true, getProperties: true, dispose: true }], route: [Route.prototype, { fallback: true, abort: true, fetch: true, fulfill: true, continue: true }], webSocket: [WebSocket.prototype, { waitForEvent: true }], - // webSocketRoute: [WebSocketRoute.prototype, { close: true }], + webSocketRoute: [WebSocketRoute.prototype, { close: true }], request: [APIRequest.prototype, { newContext: true }], requestContext: [APIRequestContext.prototype, { dispose: true, delete: true, head: true, get: true, patch: true, post: true, put: true, fetch: true, storageState: true }], response: [APIResponse.prototype, { body: true, json: true, text: true, dispose: true }], diff --git a/packages/playwright-cloudflare/src/index.ts b/packages/playwright-cloudflare/src/index.ts index aaf885d46..6beeab6f1 100644 --- a/packages/playwright-cloudflare/src/index.ts +++ b/packages/playwright-cloudflare/src/index.ts @@ -1,5 +1,3 @@ -import './patch'; - import { createInProcessPlaywright } from 'playwright-core/lib/inProcessFactory'; import { kBrowserCloseMessageId } from 'playwright-core/lib/server/chromium/crConnection'; import { env } from 'cloudflare:workers'; @@ -31,8 +29,9 @@ const HTTP_FAKE_HOST = 'http://fake.host'; const WS_FAKE_HOST = 'ws://fake.host'; const originalConnectOverCDP = playwright.chromium.connectOverCDP; -// playwright-mcp uses playwright.chromium.connectOverCDP if a CDP endpoint is passed, -// so we need to override it to use our own connectOverCDP implementation +// HACK this is a major hack, but we need it to make playwright-mcp and stagehand work without modifying their code extensively. +// Both playwright-mcp and stagehand use playwright.chromium.connectOverCDP if a CDP endpoint is passed, +// so we need to override it to use our own connectOverCDP implementation. (playwright.chromium as any).connectOverCDP = (endpointURLOrOptions: (ConnectOverCDPOptions & { wsEndpoint?: string }) | string) => { const wsEndpoint = typeof endpointURLOrOptions === 'string' ? endpointURLOrOptions : endpointURLOrOptions.wsEndpoint ?? endpointURLOrOptions.endpointURL; if (!wsEndpoint) diff --git a/packages/playwright-cloudflare/src/internal.ts b/packages/playwright-cloudflare/src/internal.ts index 7ec78041e..62e3bb3ad 100644 --- a/packages/playwright-cloudflare/src/internal.ts +++ b/packages/playwright-cloudflare/src/internal.ts @@ -8,6 +8,7 @@ import { Suite, TestCase } from 'playwright/lib/common/test'; import { rootTestType } from 'playwright/lib/common/testType'; import { WorkerMain } from 'playwright/lib/worker/workerMain'; import { TestStepInternal } from 'playwright/lib/worker/testInfo'; +import { debug } from 'playwright-core/lib/utilsBundle'; import { isUnsupportedOperationError } from './cloudflare/unsupportedOperations'; @@ -15,6 +16,7 @@ import playwright from '.'; import type { Attachment, SuiteInfo, TestCaseInfo, TestContext, TestResult } from '../internal'; import type { ClientInstrumentationListener } from 'playwright-core/lib/client/clientInstrumentation'; +import { Project } from '../types/test'; export { isUnderTest, asLocator } from 'playwright-core/lib/utils'; export { debug } from 'playwright-core/lib/utilsBundle'; @@ -23,6 +25,9 @@ export { mergeTests } from 'playwright/lib/common/testType'; export * from 'playwright-core/lib/zipBundle'; export * from 'playwright-core/lib/utilsBundle'; +// console.log must be called inside a function because it's undefined on startup +debug.log = (...args: any[]) => console.log(...args); + // @ts-ignore export const _baseTest: TestType<{}, {}> = rootTestType.test; @@ -35,7 +40,7 @@ export function setCurrentTestFile(file?: string) { } const suite = new Suite(file, 'file'); - suite._requireFile = file; + suite._requireFile = `/bundle/${file}`; suite.location = { file, line: 0, column: 0 }; setCurrentlyLoadingFileSuite(suite); _rootSuites.push(suite); @@ -68,13 +73,15 @@ export const playwrightTestConfig = { { timeout: 5000, name: 'chromium', - }, + outputDir: '/tmp/test-results', + testDir: '/bundle', + } satisfies Partial, ], }; export const configLocation = { - resolvedConfigFile: '/tmp/workerTests/playwright.config.ts', - configDir: '/tmp/workerTests', + resolvedConfigFile: '/bundle/playwright.config.ts', + configDir: '/bundle', }; async function bindSuites() { @@ -98,7 +105,7 @@ class TestWorker extends WorkerMain { workerIndex: 0, parallelIndex: 0, repeatEachIndex: 0, - projectId: playwrightTestConfig.projects[0].name, + projectId: playwrightTestConfig.projects[0].name!, config: { location: configLocation, configCLIOverrides: { @@ -200,15 +207,6 @@ export class TestRunner { } } -function paramsToRender(apiName: string) { - switch (apiName) { - case 'locator.fill': - return ['value']; - default: - return ['url', 'selector', 'text', 'key']; - } -} - const tracingGroupSteps: TestStepInternal[] = []; // adapted from _setupArtifacts fixture in packages/playwright/src/index.ts const expectApiListener: ClientInstrumentationListener = { diff --git a/packages/playwright-cloudflare/src/mocks/commander.ts b/packages/playwright-cloudflare/src/mocks/commander.ts new file mode 100644 index 000000000..617e7b724 --- /dev/null +++ b/packages/playwright-cloudflare/src/mocks/commander.ts @@ -0,0 +1 @@ +export const program = {}; \ No newline at end of file diff --git a/packages/playwright-cloudflare/src/mocks/socksProxyAgent.ts b/packages/playwright-cloudflare/src/mocks/socksProxyAgent.ts new file mode 100644 index 000000000..685f51116 --- /dev/null +++ b/packages/playwright-cloudflare/src/mocks/socksProxyAgent.ts @@ -0,0 +1,5 @@ +export class SocksProxyAgent { + constructor() { + throw new Error('not implemented'); + } +} diff --git a/packages/playwright-cloudflare/src/patch.ts b/packages/playwright-cloudflare/src/patch.ts deleted file mode 100644 index 9549d9cc8..000000000 --- a/packages/playwright-cloudflare/src/patch.ts +++ /dev/null @@ -1,3 +0,0 @@ -import process from 'process'; - -process.versions.node = '20.0.0'; diff --git a/packages/playwright-cloudflare/tests/package-lock.json b/packages/playwright-cloudflare/tests/package-lock.json index 6722af8da..aff92ea17 100644 --- a/packages/playwright-cloudflare/tests/package-lock.json +++ b/packages/playwright-cloudflare/tests/package-lock.json @@ -11,7 +11,7 @@ "devDependencies": { "@cloudflare/playwright": "file:..", "@playwright/test": "^1.54.1", - "wrangler": "^4.32.0" + "wrangler": "^4.37.1" } }, "..": { @@ -20,6 +20,7 @@ "dev": true, "license": "Apache-2.0", "devDependencies": { + "pkg-pr-new": "^0.0.59", "vite": "^6.1.0" } }, @@ -44,14 +45,14 @@ "link": true }, "node_modules/@cloudflare/unenv-preset": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.6.2.tgz", - "integrity": "sha512-C7/tW7Qy+wGOCmHXu7xpP1TF3uIhRoi7zVY7dmu/SOSGjPilK+lSQ2lIRILulZsT467ZJNlI0jBxMbd8LzkGRg==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.7.3.tgz", + "integrity": "sha512-tsQQagBKjvpd9baa6nWVIv399ejiqcrUBBW6SZx6Z22+ymm+Odv5+cFimyuCsD/fC1fQTwfRmwXBNpzvHSeGCw==", "dev": true, "license": "MIT OR Apache-2.0", "peerDependencies": { - "unenv": "2.0.0-rc.19", - "workerd": "^1.20250802.0" + "unenv": "2.0.0-rc.21", + "workerd": "^1.20250828.1" }, "peerDependenciesMeta": { "workerd": { @@ -60,9 +61,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250816.0.tgz", - "integrity": "sha512-yN1Rga4ufTdrJPCP4gEqfB47i1lWi3teY5IoeQbUuKnjnCtm4pZvXur526JzCmaw60Jx+AEWf5tizdwRd5hHBQ==", + "version": "1.20250913.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250913.0.tgz", + "integrity": "sha512-926bBGIYDsF0FraaPQV0hO9LymEN+Zdkkm1qOHxU1c58oAxr5b9Tpe4d1z1EqOD0DTFhjn7V/AxKcZBaBBhO/A==", "cpu": [ "x64" ], @@ -77,9 +78,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250816.0.tgz", - "integrity": "sha512-WyKPMQhbU+TTf4uDz3SA7ZObspg7WzyJMv/7J4grSddpdx2A4Y4SfPu3wsZleAOIMOAEVi0A1sYDhdltKM7Mxg==", + "version": "1.20250913.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250913.0.tgz", + "integrity": "sha512-uy5nJIt44CpICgfsKQotji31cn39i71e2KqE/zeAmmgYp/tzl2cXotVeDtynqqEsloox7hl/eBY5sU0x99N8oQ==", "cpu": [ "arm64" ], @@ -94,9 +95,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250816.0.tgz", - "integrity": "sha512-NWHOuFnVBaPRhLHw8kjPO9GJmc2P/CTYbnNlNm0EThyi57o/oDx0ldWLJqEHlrdEPOw7zEVGBqM/6M+V9agC6w==", + "version": "1.20250913.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250913.0.tgz", + "integrity": "sha512-khdF7MBi8L9WIt3YyWBQxipMny0J3gG824kurZiRACZmPdQ1AOzkKybDDXC3EMcF8TmGMRqKRUGQIB/25PwJuQ==", "cpu": [ "x64" ], @@ -111,9 +112,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250816.0.tgz", - "integrity": "sha512-FR+/yhaWs7FhfC3GKsM3+usQVrGEweJ9qyh7p+R6HNwnobgKr/h5ATWvJ4obGJF6ZHHodgSe+gOSYR7fkJ1xAQ==", + "version": "1.20250913.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250913.0.tgz", + "integrity": "sha512-KF5nIOt5YIYGfinY0YEe63JqaAx8WSFDHTLQpytTX+N/oJWEJu3KW6evU1TfX7o8gRlRsc0j/evcZ1vMfbDy5g==", "cpu": [ "arm64" ], @@ -128,9 +129,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250816.0.tgz", - "integrity": "sha512-0lqClj2UMhFa8tCBiiX7Zhd5Bjp0V+X8oNBG6V6WsR9p9/HlIHAGgwRAM7aYkyG+8KC8xlbC89O2AXUXLpHx0g==", + "version": "1.20250913.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250913.0.tgz", + "integrity": "sha512-m/PMnVdaUB7ymW8BvDIC5xrU16hBDCBpyf9/4y9YZSQOYTVXihxErX8kaW9H9A/I6PTX081NmxxhTbb/n+EQRg==", "cpu": [ "x64" ], @@ -158,9 +159,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", - "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", "dev": true, "license": "MIT", "optional": true, @@ -1047,9 +1048,9 @@ "license": "MIT" }, "node_modules/@sindresorhus/is": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.2.tgz", - "integrity": "sha512-d9xRovfKNz1SKieM0qJdO+PQonjnnIfSNWfHYnBSJ9hkjm0ZPw6HlxscDXYstp3z+7V2GOFHc+J0CYrYTjqCJw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.0.tgz", + "integrity": "sha512-7F/yz2IphV39hiS2zB4QYVkivrptHHh0K8qJJd9HhuWSdvf8AN7NpebW3CcDZDBQsUPMoDKWsY2WWgW7bqOcfA==", "dev": true, "license": "MIT", "engines": { @@ -1159,9 +1160,9 @@ "license": "MIT" }, "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz", + "integrity": "sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1262,9 +1263,9 @@ "license": "BSD-2-Clause" }, "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", "dev": true, "license": "MIT" }, @@ -1292,9 +1293,9 @@ } }, "node_modules/miniflare": { - "version": "4.20250816.1", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250816.1.tgz", - "integrity": "sha512-2X8yMy5wWw0dF1pNU4kztzZgp0jWv2KMqAOOb2FeQ/b11yck4aczmYHi7UYD3uyOgtj8WFhwG/KdRWAaATTtRA==", + "version": "4.20250913.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250913.0.tgz", + "integrity": "sha512-EwlUOxtvb9UKg797YZMCtNga/VSAnKG/kbJX9YGqXJoAJjDhDeAeqyCWjSl9O6EzCZNhtHuW7ZV0pD5Hec617g==", "dev": true, "license": "MIT", "dependencies": { @@ -1305,8 +1306,8 @@ "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", - "undici": "^7.10.0", - "workerd": "1.20250816.0", + "undici": "7.14.0", + "workerd": "1.20250913.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" @@ -1440,9 +1441,9 @@ } }, "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", "dev": true, "license": "MIT", "dependencies": { @@ -1461,9 +1462,9 @@ } }, "node_modules/supports-color": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.0.tgz", - "integrity": "sha512-5eG9FQjEjDbAlI5+kdpdyPIBMRH4GfTVDGREVupaZHmVoppknhM29b/S9BkQz7cathp85BVgRi/As3Siln7e0Q==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", "dev": true, "license": "MIT", "engines": { @@ -1489,9 +1490,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.15.0.tgz", - "integrity": "sha512-7oZJCPvvMvTd0OlqWsIxTuItTpJBpU1tcbVl24FMn3xt3+VSunwUasmfPJRE57oNO1KsZ4PgA1xTdAX4hq8NyQ==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.14.0.tgz", + "integrity": "sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==", "dev": true, "license": "MIT", "engines": { @@ -1499,9 +1500,9 @@ } }, "node_modules/unenv": { - "version": "2.0.0-rc.19", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.19.tgz", - "integrity": "sha512-t/OMHBNAkknVCI7bVB9OWjUUAwhVv9vsPIAGnNUxnu3FxPQN11rjh0sksLMzc3g7IlTgvHmOTl4JM7JHpcv5wA==", + "version": "2.0.0-rc.21", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.21.tgz", + "integrity": "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A==", "dev": true, "license": "MIT", "dependencies": { @@ -1513,9 +1514,9 @@ } }, "node_modules/workerd": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250816.0.tgz", - "integrity": "sha512-5gIvHPE/3QVlQR1Sc1NdBkWmqWj/TSgIbY/f/qs9lhiLBw/Da+HbNBTVYGjvwYqEb3NQ+XQM4gAm5b2+JJaUJg==", + "version": "1.20250913.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250913.0.tgz", + "integrity": "sha512-y3J1NjCL10SAWDwgGdcNSRyOVod/dWNypu64CCdjj8VS4/k+Ofa/fHaJGC1stbdzAB1tY2P35Ckgm1PU5HKWiw==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -1526,28 +1527,28 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20250816.0", - "@cloudflare/workerd-darwin-arm64": "1.20250816.0", - "@cloudflare/workerd-linux-64": "1.20250816.0", - "@cloudflare/workerd-linux-arm64": "1.20250816.0", - "@cloudflare/workerd-windows-64": "1.20250816.0" + "@cloudflare/workerd-darwin-64": "1.20250913.0", + "@cloudflare/workerd-darwin-arm64": "1.20250913.0", + "@cloudflare/workerd-linux-64": "1.20250913.0", + "@cloudflare/workerd-linux-arm64": "1.20250913.0", + "@cloudflare/workerd-windows-64": "1.20250913.0" } }, "node_modules/wrangler": { - "version": "4.32.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.32.0.tgz", - "integrity": "sha512-q7TRSavBW3Eg3pp4rxqKJwSK+u/ieFOBdNvUsq1P1EMmyj3//tN/iXDokFak+dkW0vDYjsVG3PfOfHxU92OS6w==", + "version": "4.37.1", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.37.1.tgz", + "integrity": "sha512-ntm1OsIB2r/f7b5bfS84Lzz5QEx3zn4vUsn1JOVz/+7bw8triyytnxbp68OwOimF1vL5A9sQ0Nd+L6u8F3hECg==", "dev": true, "license": "MIT OR Apache-2.0", "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", - "@cloudflare/unenv-preset": "2.6.2", + "@cloudflare/unenv-preset": "2.7.3", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", - "miniflare": "4.20250816.1", + "miniflare": "4.20250913.0", "path-to-regexp": "6.3.0", - "unenv": "2.0.0-rc.19", - "workerd": "1.20250816.0" + "unenv": "2.0.0-rc.21", + "workerd": "1.20250913.0" }, "bin": { "wrangler": "bin/wrangler.js", @@ -1560,7 +1561,7 @@ "fsevents": "~2.3.2" }, "peerDependencies": { - "@cloudflare/workers-types": "^4.20250816.0" + "@cloudflare/workers-types": "^4.20250913.0" }, "peerDependenciesMeta": { "@cloudflare/workers-types": { diff --git a/packages/playwright-cloudflare/tests/package.json b/packages/playwright-cloudflare/tests/package.json index e7649ca29..7192faa00 100644 --- a/packages/playwright-cloudflare/tests/package.json +++ b/packages/playwright-cloudflare/tests/package.json @@ -8,6 +8,6 @@ "devDependencies": { "@cloudflare/playwright": "file:..", "@playwright/test": "^1.54.1", - "wrangler": "^4.32.0" + "wrangler": "^4.37.1" } } diff --git a/packages/playwright-cloudflare/tests/src/browser-rendering/session-management.spec.ts b/packages/playwright-cloudflare/tests/src/browser-rendering/session-management.spec.ts index 81a882c89..fa46b94ef 100644 --- a/packages/playwright-cloudflare/tests/src/browser-rendering/session-management.spec.ts +++ b/packages/playwright-cloudflare/tests/src/browser-rendering/session-management.spec.ts @@ -121,11 +121,3 @@ test(`should launch browser with persistent context is persistent=true`, async ( expect(browser.contexts()).toHaveLength(1); await browser.close(); }); - -test(`should launch the browser with a specific user agent`, async ({ page }) => { - await page.setContent(``); - await page.evaluate('document.write(navigator.userAgent)'); - expect(await page.content()).toContain( - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36' - ); -}); diff --git a/packages/playwright-cloudflare/tests/src/browser-rendering/user-agent.spec.ts b/packages/playwright-cloudflare/tests/src/browser-rendering/user-agent.spec.ts new file mode 100644 index 000000000..5e36af4b4 --- /dev/null +++ b/packages/playwright-cloudflare/tests/src/browser-rendering/user-agent.spec.ts @@ -0,0 +1,22 @@ +import { test, expect } from '../server/workerFixtures'; + +test(`page should have a default user agent`, async ({ page }) => { + await page.setContent(``); + expect(await page.evaluate(() => navigator.userAgent)).toContain( + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36' + ); +}); + +test(`context should be able to specify a user agent`, async ({ browser, server }) => { + await using context = await browser.newContext({ + userAgent: 'foobar', + }); + await using page = await context.newPage(); + await page.setContent(``); + expect(await page.evaluate('navigator.userAgent')).toContain('foobar'); + const [request] = await Promise.all([ + page.waitForRequest('**/empty.html'), + page.goto(server.EMPTY_PAGE), + ]); + expect(request.headers()['user-agent']).toBe('foobar'); +}); diff --git a/packages/playwright-cloudflare/tests/src/server/testsServer.ts b/packages/playwright-cloudflare/tests/src/server/testsServer.ts index ecfc54a89..3dc538504 100644 --- a/packages/playwright-cloudflare/tests/src/server/testsServer.ts +++ b/packages/playwright-cloudflare/tests/src/server/testsServer.ts @@ -1,8 +1,8 @@ -import { TestRunner, TestEndPayload, isUnderTest } from '@cloudflare/playwright/internal'; +import { TestRunner, TestEndPayload, isUnderTest, TestInfoError, TestResult } from '@cloudflare/playwright/internal'; import { DurableObject } from 'cloudflare:workers'; import '@workerTests/index'; -import { skipTests } from '../skipTests'; +import { skipTests, skipErrorMessages } from '../skipTests'; import { BrowserBindingName } from '../utils'; export type TestRequestPayload = { @@ -17,6 +17,17 @@ const log = console.log.bind(console); const skipTestsFullTitles = new Set(skipTests.map(t => t.join(' > '))); +function formatError(error: TestInfoError | Error | string) { + if (typeof error === 'string') + return error; + return `${error.message}${error.stack ? `\n${error.stack}` : ''}`; +} + +function shouldSkipTestResult(testResult: TestResult) { + const errorText = testResult.errors.map(e => formatError(e)).join('\n'); + return skipErrorMessages.some(msg => typeof msg === 'string' ? errorText.includes(msg) : msg.test(errorText)); +} + export class TestsServer extends DurableObject { constructor(state: DurableObjectState, env: Env) { super(state, env); @@ -35,7 +46,7 @@ export class TestsServer extends DurableObject { const timeout = parseInt(url.searchParams.get('timeout') ?? '10', 10) * 1000; const { testId, fullTitle, retry } = await request.json() as TestRequestPayload; - const assetsUrl = url.origin; + const assetsUrl = url.origin.replace(/^http:/, 'https:'); const { env } = this; const context = { env, sessionId, assetsUrl, retry, binding }; const testRunner = new TestRunner(context, { timeout }); @@ -61,8 +72,29 @@ export class TestsServer extends DurableObject { log(`🧪 Running ${fullTitle}${retry ? ` (retry #${retry})` : ''}`); const result = await testRunner.runTest(file, testId); - if (!['passed', 'skipped'].includes(result.status)) - log(`❌ ${fullTitle} failed with status ${result.status}${result.errors.length ? `: ${result.errors[0].message}` : ''}`); + + if (!['passed', 'skipped'].includes(result.status)) { + if (shouldSkipTestResult(result)) { + log(`🚫 Skipping ${fullTitle} because it failed with a known error message`); + return Response.json({ + testId, + status: 'skipped', + errors: result.errors, + annotations: [ + { + type: 'skip', + description: `Test skipped because it failed with a known error message`, + } + ], + duration: 0, + hasNonRetriableError: false, + timeout, + expectedStatus: 'skipped' + } satisfies TestEndPayload); + } + const [error] = result.errors; + log(`❌ ${fullTitle} failed with status ${result.status}${error ? `: ${formatError(error)}` : ''}`); + } return Response.json(result); } } diff --git a/packages/playwright-cloudflare/tests/src/server/workerFixtures.ts b/packages/playwright-cloudflare/tests/src/server/workerFixtures.ts index 9cbde905e..a34dd49e1 100644 --- a/packages/playwright-cloudflare/tests/src/server/workerFixtures.ts +++ b/packages/playwright-cloudflare/tests/src/server/workerFixtures.ts @@ -1,7 +1,8 @@ +import fs from 'fs'; + import { _baseTest, currentTestContext, runWithExpectApiListener } from '@cloudflare/playwright/internal'; import playwright, { connect } from '@cloudflare/playwright'; import { env } from 'cloudflare:workers'; -import fs from '@cloudflare/playwright/fs'; import { expect as baseExpect } from '@cloudflare/playwright/test'; import type { BrowserBindingName } from '../utils'; @@ -20,8 +21,8 @@ export type WorkersWorkerFixtures = { bindingName: BrowserBindingName; binding: BrowserWorker; cdnTraces: { - worker: CdnTrace; - browser: CdnTrace; + worker?: CdnTrace; + browser?: CdnTrace; }; }; @@ -68,8 +69,7 @@ class TestServer { } get PORT() { - this._testInfo.skip(true, 'server.PORT not supported, skipping'); - return 443; + return Number(new URL(this.PREFIX).port); } onceWebSocketConnection() { @@ -111,6 +111,10 @@ class TestServer { sendOnWebSocketConnection() { this._testInfo.skip(true, 'sendOnWebSocketConnection not supported, skipping'); } + + setExtraHeaders() { + this._testInfo.skip(true, 'setExtraHeaders not supported, skipping'); + } } export type ServerFixtures = { @@ -177,10 +181,11 @@ export type TestModeTestFixtures = { }; function parseTrace(trace: string) { - return Object.fromEntries(trace.split('\n').filter(line => line).map(line => { + const { loc, colo } = Object.fromEntries(trace.split('\n').filter(line => line).map(line => { const [key, value] = line.split('='); return [key, value]; - })) as { loc: string, colo: string }; + })) as { loc?: string, colo?: string }; + return loc && colo ? { loc, colo } : undefined; } export const test = platformTest.extend({ @@ -214,15 +219,15 @@ export const test = platformTest.extend { - const worker = parseTrace(await fetch('https://1.1.1.1/cdn-cgi/trace').then(resp => resp.text())); + const worker = parseTrace(await fetch('https://1.1.1.1/cdn-cgi/trace').then(resp => resp.text()).catch(() => '')); const context = await browser.newContext(); const page = await context.newPage(); - const browserCdnTrace = parseTrace(await page.goto('https://1.1.1.1/cdn-cgi/trace').then(resp => resp!.text())); + const browserCdnTrace = parseTrace(await page.goto('https://1.1.1.1/cdn-cgi/trace').then(resp => resp!.text()).catch(() => '')); await page.close(); await context.close(); // eslint-disable-next-line no-console - console.log(`ℹ️ Session ID: ${sessionId}, Worker: ${worker.colo}, Browser: ${browserCdnTrace.colo}`); + console.log(`ℹ️ Session ID: ${sessionId}, Worker: ${worker ? worker.colo : 'N/A'}, Browser: ${browserCdnTrace ? browserCdnTrace.colo : 'N/A'}`); await run({ worker, browser: browserCdnTrace }); }, { scope: 'worker' }], @@ -326,7 +331,7 @@ export const test = platformTest.extend { - await run((p: string) => `/assets/${p}`); + await run((p: string) => `/bundle/assets/${p}`); }, mode: ['service', { scope: 'worker', option: true }], @@ -343,7 +348,7 @@ export const test = platformTest.extend { + contextFactory: async ({ browser, asset }, run) => { const contexts: BrowserContext[] = []; await run(async options => { const context = await browser.newContext(options); @@ -391,10 +396,10 @@ export const test = platformTest.extend = [ + 'Cloudflare Workers does not support', + '__filename is not defined', + '__dirname is not defined', + `\\(\\) => {}`, // Cloudflare serializes empty arrow function with new lines + 'createHttp2Server is not defined', + /\[unenv\] .* is not implemented yet!/, + /Received string:\s+"Cloudflare-Workers"/, + '.rendering.cfdata.org', ]; diff --git a/packages/playwright-cloudflare/tests/vite.config.ts b/packages/playwright-cloudflare/tests/vite.config.ts index 5f1d3c6a9..ea9cabd18 100644 --- a/packages/playwright-cloudflare/tests/vite.config.ts +++ b/packages/playwright-cloudflare/tests/vite.config.ts @@ -42,6 +42,7 @@ export default defineConfig({ 'dns': 'node:dns', 'domain': 'node:domain', 'events': 'node:events', + 'fs': 'node:fs', 'http': 'node:http', 'http2': 'node:http2', 'https': 'node:https', @@ -81,7 +82,6 @@ export default defineConfig({ 'packages/playwright-core/lib': path.resolve(basedir, '../../playwright-core/src'), 'playwright-core': '@cloudflare/playwright', '@playwright/test': path.resolve(basedir, './src/server/workerFixtures'), - 'fs': '@cloudflare/playwright/fs', '@isomorphic': path.resolve(basedir, '../../playwright-core/src/utils/isomorphic'), '@testIsomorphic': path.resolve(basedir, '../../playwright/src/isomorphic'), }, @@ -120,6 +120,7 @@ export default defineConfig({ 'node:dns', 'node:domain', 'node:events', + 'node:fs', 'node:http', 'node:http2', 'node:https', diff --git a/packages/playwright-cloudflare/tests/wrangler.toml b/packages/playwright-cloudflare/tests/wrangler.toml index d6da1bf2a..c762edd19 100644 --- a/packages/playwright-cloudflare/tests/wrangler.toml +++ b/packages/playwright-cloudflare/tests/wrangler.toml @@ -1,10 +1,16 @@ name = "playwright-test-workers" main = "src/server/index.ts" -workers_dev = false -compatibility_flags = ["nodejs_compat"] -compatibility_date = "2025-03-05" +compatibility_flags = ["nodejs_compat", "enable_nodejs_fs_module"] +compatibility_date = "2025-09-01" upload_source_maps = true +base_dir = "../../../tests" +find_additional_modules = true + +rules = [ + { type = "Data", globs = ["assets/**/*", "config/testserver/*", "**/*-snapshots/**/*-chromium.*"], fallthrough = true } +] + routes = [ { pattern = "playwright-test-workers.rendering.cfdata.org", custom_domain = true }, { pattern = "playwright-test-workers-cross-origin.rendering.cfdata.org", custom_domain = true }, diff --git a/packages/playwright-cloudflare/utils/build_bundles.js b/packages/playwright-cloudflare/utils/build_bundles.js index 64b3e9109..ad8b36d44 100644 --- a/packages/playwright-cloudflare/utils/build_bundles.js +++ b/packages/playwright-cloudflare/utils/build_bundles.js @@ -7,7 +7,6 @@ const bundles = { // Error: Class constructor Inflate cannot be invoked without 'new' // It needs to build before other bundles, because they may depend on it 'pngjs': '../bundles/pngjs', - 'fs': '../bundles/fs', 'utilsBundleImpl': '../../playwright-core/bundles/utils', 'zipBundleImpl': '../../playwright-core/bundles/zip', 'expectBundleImpl': '../../playwright/bundles/expect', @@ -21,6 +20,7 @@ const external = [ 'crypto', 'dns', 'events', + 'fs', 'http', 'https', 'module', @@ -49,6 +49,9 @@ const basedir = path.dirname(fileURLToPath(import.meta.url)); 'node:fs': 'fs', 'node:process': 'process', + 'commander': path.join(basedir, '../src/mocks/commander'), + 'socks-proxy-agent': path.join(basedir, '../src/mocks/socksProxyAgent'), + ...(name === 'pngjs' ? { 'zlib': 'browserify-zlib' } : { 'pngjs': path.join(basedir, `../src/bundles/pngjs.js` ) }), }, @@ -68,9 +71,8 @@ const basedir = path.dirname(fileURLToPath(import.meta.url)); 'formidable', 'ansi-styles', - ...(name === 'fs' ? [...external, 'zlib'] - : name === 'pngjs' ? [...external, 'fs'] - : [...external, 'fs', 'zlib', 'pngjs']) + ...(name === 'pngjs' ? external + : [...external, 'zlib', 'pngjs']) ], output: { dir: path.join(basedir, '../src/bundles'), diff --git a/packages/playwright-cloudflare/utils/generate_worker_tests.js b/packages/playwright-cloudflare/utils/generate_worker_tests.js index e1edf015f..d21c6c6fe 100644 --- a/packages/playwright-cloudflare/utils/generate_worker_tests.js +++ b/packages/playwright-cloudflare/utils/generate_worker_tests.js @@ -1,20 +1,17 @@ import path from "path"; import { fileURLToPath } from "url"; -import { decodeBase64ToFiles, deleteDir, encodeFilesToBase64, listFiles, writeFile } from "./utils.js"; +import { deleteDir, listFiles, writeFile } from "./utils.js"; const basedir = path.dirname(fileURLToPath(import.meta.url)); const excludedFiles = [ - 'page/interception.spec.ts', 'page/page-leaks.spec.ts', - 'library/browsertype-connect.spec.ts', 'library/browsertype-launch-selenium.spec.ts', 'library/browsertype-launch-server.spec.ts', 'library/browsertype-launch.spec.ts', 'library/client-certificates.spec.ts', 'library/debug-controller.spec.ts', - 'library/har.spec.ts', 'library/headful.spec.ts', 'library/launcher.spec.ts', 'library/snapshotter.spec.ts', @@ -27,57 +24,22 @@ const sourceTestsDir = path.join(basedir, '..', '..', '..', 'tests'); const cloudflareSourceTestsDir = path.join(basedir, '..', 'tests', 'src'); const workerTestsDir = path.join(basedir, '..', 'tests', 'workerTests'); -function setTestFilePlugin() { - return { - name: 'transform-file', - transform(src, id) { - let testPath = [sourceTestsDir, cloudflareSourceTestsDir].map(dir => path.relative(dir, id).replace(/\\/g, '/')) - .find(p => !p.startsWith('..')); - if (/\.(spec|test)\.ts$/.test(id)) { - return { - code: [ - `import { setCurrentTestFile } from '@cloudflare/playwright/internal';setCurrentTestFile(${JSON.stringify(testPath)});`, - src, - 'setCurrentTestFile(undefined);', - ].join('\n'), - map: null, // provide source map if available - } - } - }, - } -} - deleteDir(workerTestsDir); -// generate workerTests/assets.ts file -const assets = [ - ...['page', 'library'].flatMap(dir => listFiles(path.join(sourceTestsDir, dir), { recursive: true })).filter(file => /-chromium\.(png|jpg)/.test(file)), - ...listFiles(path.join(sourceTestsDir, 'assets'), { recursive: true }), - ] - .map(file => path.relative(sourceTestsDir, file).replace(/\\/g, '/')); - -writeFile(path.join(workerTestsDir, 'assets.ts'), `// @ts-nocheck -import path from 'path'; -import zlib from 'zlib'; - -import fs from '@cloudflare/playwright/fs'; - -${decodeBase64ToFiles.toString()} - -decodeBase64ToFiles('/', ${JSON.stringify(encodeFilesToBase64(sourceTestsDir, assets), undefined, 2)}); -`); - // generate workerTests/index.ts file const testFiles = ['page', 'library'] .flatMap(dir => listFiles(path.join(sourceTestsDir, dir))) .filter(file => /\.(test|spec)\.ts$/.test(file)) .filter(file => !excludedFiles.includes(path.relative(sourceTestsDir, file).replace(/\\/g, '/'))) .map(file => `@workerTests/${path.relative(sourceTestsDir, file)}`.replace(/\\/g, '/').replace(/\.ts$/, '')); + const cloudflareTestFiles = listFiles(cloudflareSourceTestsDir, { recursive: true }) .filter(file => /\.(test|spec)\.ts$/.test(file)) .map(file => `@cloudflareTests/${path.relative(cloudflareSourceTestsDir, file)}`.replace(/\\/g, '/').replace(/\.ts$/, '')); -writeFile(path.join(workerTestsDir, 'index.ts'), `import "./assets"; - -${[...testFiles, ...cloudflareTestFiles].map(file => `import ${JSON.stringify(file)};`).join('\n')} -`); +writeFile(path.join( + workerTestsDir, 'index.ts'), + [...testFiles, ...cloudflareTestFiles] + .map(file => `import ${JSON.stringify(file)};`) + .join('\n') +); diff --git a/packages/playwright-cloudflare/utils/utils.js b/packages/playwright-cloudflare/utils/utils.js index e4dab6f5c..3e674006c 100644 --- a/packages/playwright-cloudflare/utils/utils.js +++ b/packages/playwright-cloudflare/utils/utils.js @@ -1,6 +1,5 @@ import fs from 'fs'; import path from 'path'; -import zlib from 'zlib'; export function writeFile(filePath, content) { fs.mkdirSync(path.dirname(filePath), { recursive: true }); @@ -24,30 +23,3 @@ export function listFiles(dir, options) { } return files; } - -export function encodeFilesToBase64(rootPath, relativePaths) { - const result = {}; - - for (const relPath of relativePaths) { - const fullPath = path.join(rootPath, relPath); - if (!fs.existsSync(fullPath) || fs.statSync(fullPath).isDirectory()) - continue; - - const content = fs.readFileSync(fullPath); // buffer - const compressed = zlib.gzipSync(content); - result[relPath] = compressed.toString('base64'); - } - - return result; -} - -export function decodeBase64ToFiles(outputFolder, filesObject) { - for (const [relPath, base64Data] of Object.entries(filesObject)) { - const compressed = Buffer.from(base64Data, 'base64'); - const decompressed = zlib.gunzipSync(compressed); - const fullPath = path.join(outputFolder, relPath); - - fs.mkdirSync(path.dirname(fullPath), { recursive: true }); - fs.writeFileSync(fullPath, decompressed); - } -} diff --git a/packages/playwright-cloudflare/vite.config.ts b/packages/playwright-cloudflare/vite.config.ts index 6784bd764..5de56f876 100644 --- a/packages/playwright-cloudflare/vite.config.ts +++ b/packages/playwright-cloudflare/vite.config.ts @@ -21,6 +21,7 @@ export default defineConfig({ 'crypto': 'node:crypto', 'dns': 'node:dns', 'events': 'node:events', + 'fs': 'node:fs', 'http': 'node:http', 'http2': 'node:http2', 'https': 'node:https', @@ -41,7 +42,6 @@ export default defineConfig({ './utilsBundleImpl': path.resolve(__dirname, './src/bundles/utilsBundleImpl'), './zipBundleImpl': path.resolve(__dirname, './src/bundles/zipBundleImpl'), './expectBundleImpl': path.resolve(__dirname, './src/bundles/expectBundleImpl'), - 'fs': path.resolve(__dirname, './src/bundles/fs'), 'pngjs': path.resolve(__dirname, './src/bundles/pngjs'), // replace playwright transport with cloudflare workers transport @@ -82,25 +82,15 @@ export default defineConfig({ // prevents __defProp, __defNormalProp, __publicField in compiled code target: 'esnext', rollupOptions: { - output: [ - { - format: 'es', - dir: 'lib/esm', - preserveModules: true, - preserveModulesRoot: 'src', - entryFileNames: '[name].js', - chunkFileNames: '[name].js', - }, - { - format: 'cjs', - dir: 'lib/cjs', - preserveModules: true, - preserveModulesRoot: 'src', - entryFileNames: '[name].js', - chunkFileNames: '[name].js', - exports: 'named', - }, - ], + output: [{ + format: 'es', + dir: 'lib', + preserveModules: true, + preserveModulesRoot: 'src', + entryFileNames: '[name].js', + chunkFileNames: '[name].js', + exports: 'named', + }], external: [ 'node:async_hooks', 'node:assert', @@ -111,6 +101,7 @@ export default defineConfig({ 'node:crypto', 'node:dns', 'node:events', + 'node:fs', 'node:http', 'node:http2', 'node:https', diff --git a/packages/playwright-core/src/server/har/harRecorder.ts b/packages/playwright-core/src/server/har/harRecorder.ts index 7d99a3779..fefdcd98e 100644 --- a/packages/playwright-core/src/server/har/harRecorder.ts +++ b/packages/playwright-core/src/server/har/harRecorder.ts @@ -86,7 +86,9 @@ export class HarRecorder implements HarTracerDelegate { (this._zipFile as unknown as EventEmitter).on('error', error => result.reject(error)); this._zipFile.addBuffer(Buffer.from(harFileContent, 'utf-8'), 'har.har'); this._zipFile.end(); - this._zipFile.outputStream.pipe(fs.createWriteStream(this._artifact.localPath())).on('close', () => { + const chunks: Buffer[] = []; + this._zipFile.outputStream.on('data', data => chunks.push(data)).on('close', () => { + fs.writeFileSync(this._artifact.localPath(), Buffer.concat(chunks)); result.resolve(); }); await result; diff --git a/packages/playwright-core/src/server/localUtils.ts b/packages/playwright-core/src/server/localUtils.ts index 5fb9b57d4..5df007a11 100644 --- a/packages/playwright-core/src/server/localUtils.ts +++ b/packages/playwright-core/src/server/localUtils.ts @@ -85,10 +85,15 @@ export async function zip(progress: Progress, stackSessions: Map { - zipFile.outputStream.pipe(fs.createWriteStream(params.zipFile)) - .on('close', () => promise.resolve()) + const chunks: Buffer[] = []; + zipFile.outputStream + .on('data', data => chunks.push(data)) + .on('close', () => { + fs.writeFileSync(params.zipFile, Buffer.concat(chunks)); + promise.resolve(); + }) .on('error', error => promise.reject(error)); - }); + }); await progress.race(promise); await deleteStackSession(progress, stackSessions, params.stacksId); return; @@ -114,8 +119,10 @@ export async function zip(progress: Progress, stackSessions: Map { - zipFile.outputStream.pipe(fs.createWriteStream(params.zipFile)).on('close', () => { + const chunks: Buffer[] = []; + zipFile.outputStream.on('data', data => chunks.push(data)).on('close', () => { fs.promises.unlink(tempFile).then(() => { + fs.writeFileSync(params.zipFile, Buffer.concat(chunks)); promise.resolve(); }).catch(error => promise.reject(error)); }); diff --git a/packages/playwright-core/src/server/utils/fileUtils.ts b/packages/playwright-core/src/server/utils/fileUtils.ts index 3eab2c036..fab20db31 100644 --- a/packages/playwright-core/src/server/utils/fileUtils.ts +++ b/packages/playwright-core/src/server/utils/fileUtils.ts @@ -193,9 +193,13 @@ export class SerializedFS { for (const entry of op.entries) zipFile.addFile(entry.value, entry.name); zipFile.end(); + const chunks: Buffer[] = []; zipFile.outputStream - .pipe(fs.createWriteStream(op.zipFileName)) - .on('close', () => result.resolve()) + .on('data', data => chunks.push(data)) + .on('close', () => { + fs.writeFileSync(op.zipFileName, Buffer.concat(chunks)); + result.resolve(); + }) .on('error', error => result.reject(error)); await result; return; diff --git a/tests/library/har.spec.ts b/tests/library/har.spec.ts index db5b14a5d..f386b093f 100644 --- a/tests/library/har.spec.ts +++ b/tests/library/har.spec.ts @@ -22,7 +22,7 @@ import type { BrowserContext, BrowserContextOptions } from 'playwright-core'; import type { AddressInfo } from 'net'; import type { Log } from '../../packages/trace/src/har'; import { parseHar } from '../config/utils'; -const { createHttp2Server } = require('../../packages/playwright-core/lib/utils'); +// const { createHttp2Server } = require('../../packages/playwright-core/lib/utils'); async function pageWithHar(contextFactory: (options?: BrowserContextOptions) => Promise, testInfo: any, options: { outputPath?: string } & Partial> = {}) { const harPath = testInfo.outputPath(options.outputPath || 'test.har');