diff --git a/.gitignore b/.gitignore index f3bab778..4181162b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ cursorrules.md node_modules/ .pnp .pnp.js +.pnpm-store/ # Python __pycache__/ @@ -38,3 +39,29 @@ npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* + +# TypeScript +*.tsbuildinfo +dist/ +build/ +*.js.map + +# Go +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out +go.work + +# Coverage +coverage/ +*.lcov +.nyc_output/ + +# Misc +*.tmp +*.temp +.cache/ diff --git a/package.json b/package.json index ee1334cb..06520c28 100644 --- a/package.json +++ b/package.json @@ -23,5 +23,9 @@ "prettier": "^3.2.5", "typescript-eslint": "^8.50.1" }, - "packageManager": "pnpm@9.0.0" + "dependencies": { + "@browserbasehq/sdk": "^2.6.0", + "dotenv": "^17.2.3", + "playwright-core": "^1.57.0" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0dc80696..c8b8ae83 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,16 @@ settings: importers: .: + dependencies: + '@browserbasehq/sdk': + specifier: ^2.6.0 + version: 2.6.0 + dotenv: + specifier: ^17.2.3 + version: 17.2.3 + playwright-core: + specifier: ^1.57.0 + version: 1.57.0 devDependencies: '@typescript-eslint/eslint-plugin': specifier: ^8.50.1 @@ -29,6 +39,9 @@ importers: packages: + '@browserbasehq/sdk@2.6.0': + resolution: {integrity: sha512-83iXP5D7xMm8Wyn66TUaUrgoByCmAJuoMoZQI3sGg3JAiMlTfnCIMqyVBoNSaItaPIkaCnrsj6LiusmXV2X9YA==} + '@eslint-community/eslint-utils@4.9.0': resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -89,6 +102,12 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node-fetch@2.6.13': + resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} + + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + '@typescript-eslint/eslint-plugin@8.50.1': resolution: {integrity: sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -148,6 +167,10 @@ packages: resolution: {integrity: sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -158,6 +181,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -168,6 +195,9 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -177,6 +207,10 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -192,6 +226,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -211,6 +249,34 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -263,6 +329,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -296,6 +366,28 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -304,10 +396,29 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -362,6 +473,18 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -375,6 +498,20 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -403,6 +540,11 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + playwright-core@1.57.0: + resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} + engines: {node: '>=18'} + hasBin: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -445,6 +587,9 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -467,9 +612,22 @@ packages: engines: {node: '>=14.17'} hasBin: true + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -485,6 +643,18 @@ packages: snapshots: + '@browserbasehq/sdk@2.6.0': + dependencies: + '@types/node': 18.19.130 + '@types/node-fetch': 2.6.13 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2)': dependencies: eslint: 9.39.2 @@ -546,6 +716,15 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/node-fetch@2.6.13': + dependencies: + '@types/node': 18.19.130 + form-data: 4.0.5 + + '@types/node@18.19.130': + dependencies: + undici-types: 5.26.5 + '@typescript-eslint/eslint-plugin@8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -637,12 +816,20 @@ snapshots: '@typescript-eslint/types': 8.50.1 eslint-visitor-keys: 4.2.1 + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 acorn@8.15.0: {} + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -656,6 +843,8 @@ snapshots: argparse@2.0.1: {} + asynckit@0.4.0: {} + balanced-match@1.0.2: {} brace-expansion@1.1.12: @@ -667,6 +856,11 @@ snapshots: dependencies: balanced-match: 1.0.2 + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + callsites@3.1.0: {} chalk@4.1.2: @@ -680,6 +874,10 @@ snapshots: color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + concat-map@0.0.1: {} cross-spawn@7.0.6: @@ -694,6 +892,31 @@ snapshots: deep-is@0.1.4: {} + delayed-stream@1.0.0: {} + + dotenv@17.2.3: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + escape-string-regexp@4.0.0: {} eslint-config-prettier@10.1.8(eslint@9.39.2): @@ -766,6 +989,8 @@ snapshots: esutils@2.0.3: {} + event-target-shim@5.0.1: {} + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} @@ -792,14 +1017,65 @@ snapshots: flatted@3.3.3: {} + form-data-encoder@1.7.2: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 globals@14.0.0: {} + gopd@1.2.0: {} + has-flag@4.0.0: {} + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -844,6 +1120,14 @@ snapshots: lodash.merge@4.6.2: {} + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -856,6 +1140,12 @@ snapshots: natural-compare@1.4.0: {} + node-domexception@1.0.0: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -883,6 +1173,8 @@ snapshots: picomatch@4.0.3: {} + playwright-core@1.57.0: {} + prelude-ls@1.2.1: {} prettier@3.7.4: {} @@ -910,6 +1202,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tr46@0.0.3: {} + ts-api-utils@2.1.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -931,10 +1225,21 @@ snapshots: typescript@5.9.3: {} + undici-types@5.26.5: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 + web-streams-polyfill@4.0.0-beta.3: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/python/playwright/proxies/.env.example b/python/playwright/proxies/.env.example new file mode 100644 index 00000000..0f885714 --- /dev/null +++ b/python/playwright/proxies/.env.example @@ -0,0 +1,7 @@ +BROWSERBASE_API_KEY=your_api_key_here +BROWSERBASE_PROJECT_ID=your_project_id_here + +# Optional: For custom external proxies (uncomment test in main.py to use) +# CUSTOM_PROXY_SERVER=http://your-proxy-server:port +# CUSTOM_PROXY_USERNAME=your_username +# CUSTOM_PROXY_PASSWORD=your_password \ No newline at end of file diff --git a/python/playwright/proxies/README.md b/python/playwright/proxies/README.md new file mode 100644 index 00000000..53553291 --- /dev/null +++ b/python/playwright/proxies/README.md @@ -0,0 +1,57 @@ +# Browserbase Proxy Testing with Playwright + +## AT A GLANCE + +- Goal: demonstrate different proxy configurations with Browserbase sessions using pure Playwright. + +## GLOSSARY + +- Proxies: Browserbase's default proxy rotation for enhanced privacy + Docs → https://docs.browserbase.com/features/proxies + +## QUICKSTART + +1. `cd python/playwright/proxies` +2. `uv sync` (or `pip install .`) +3. `playwright install chromium` +4. Copy `.env.example` to `.env` and add your `BROWSERBASE_API_KEY` and `BROWSERBASE_PROJECT_ID` +5. `uv run python main.py` (or `python main.py`) + +## EXPECTED OUTPUT + +- Tests built-in proxy rotation +- Tests geolocation-specific proxies (New York) +- Tests custom external proxies (commented out by default; requires `CUSTOM_PROXY_SERVER`, `CUSTOM_PROXY_USERNAME`, `CUSTOM_PROXY_PASSWORD`) +- Displays IP information and geolocation data for each test +- Shows how different proxy configurations affect your apparent location + +## COMMON PITFALLS + +- Browserbase Developer plan or higher is required to use proxies +- "ModuleNotFoundError": ensure all dependencies are installed via `uv sync` or `pip install .` +- Missing credentials: verify `.env` contains `BROWSERBASE_PROJECT_ID` and `BROWSERBASE_API_KEY` +- Custom proxy errors: verify external proxy server credentials and availability +- Playwright not installed: run `playwright install chromium` after pip install + +## USE CASES + +• Geo-testing: Verify location-specific content, pricing, or compliance banners. +• Scraping at scale: Rotate IPs to reduce blocks and increase CAPTCHA success rates. +• Custom routing: Mix built-in and external proxies, or apply domain-based rules for compliance. + +## NEXT STEPS + +• Add routing rules: Configure domainPattern to direct specific sites through targeted proxies. +• Test multiple geos: Compare responses from different cities/countries and log differences. +• Improve reliability: Add retries and fallbacks to handle proxy errors like ERR_TUNNEL_CONNECTION_FAILED. + +## HELPFUL RESOURCES + +📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction +📚 Playwright Docs: https://playwright.dev/python/docs/intro +🎮 Browserbase: https://www.browserbase.com +💡 Try it out: https://www.browserbase.com/playground +🔧 Templates: https://www.browserbase.com/templates +📧 Need help? support@browserbase.com +💬 Discord: http://stagehand.dev/discord + diff --git a/python/playwright/proxies/main.py b/python/playwright/proxies/main.py new file mode 100644 index 00000000..4e9cd5d0 --- /dev/null +++ b/python/playwright/proxies/main.py @@ -0,0 +1,170 @@ +# Browserbase Proxy Testing with Playwright - See README.md for full documentation + +import asyncio +import json +import os +from dataclasses import dataclass + +from browserbase import Browserbase +from dotenv import load_dotenv +from playwright.async_api import async_playwright + +load_dotenv() + + +@dataclass +class GeoInfo: # noqa: F841 + """IP and geolocation data from ipinfo.io (for reference)""" + + ip: str | None = None + city: str | None = None + region: str | None = None + country: str | None = None + loc: str | None = None + timezone: str | None = None + org: str | None = None + postal: str | None = None + hostname: str | None = None + + +BROWSERBASE_API_KEY = os.environ.get("BROWSERBASE_API_KEY") +BROWSERBASE_PROJECT_ID = os.environ.get("BROWSERBASE_PROJECT_ID") + +if not BROWSERBASE_API_KEY: + raise ValueError("BROWSERBASE_API_KEY environment variable is required") +if not BROWSERBASE_PROJECT_ID: + raise ValueError("BROWSERBASE_PROJECT_ID environment variable is required") + +bb = Browserbase(api_key=BROWSERBASE_API_KEY) + + +def create_session_with_built_in_proxies(): + """Use Browserbase's default proxy rotation for enhanced privacy and IP diversity.""" + session = bb.sessions.create( + project_id=BROWSERBASE_PROJECT_ID, + proxies=True, # Enables automatic proxy rotation across different IP addresses. + ) + return session + + +def create_session_with_geo_location(): + """Route traffic through specific geographic location to test location-based restrictions.""" + session = bb.sessions.create( + project_id=BROWSERBASE_PROJECT_ID, + proxies=[ + { + "type": "browserbase", # Use Browserbase's managed proxy infrastructure. + "geolocation": { + "city": "NEW_YORK", # Simulate traffic from New York for testing geo-specific content. + "state": "NY", # See https://docs.browserbase.com/features/proxies for more geolocation options. + "country": "US", + }, + } + ], + ) + return session + + +def create_session_with_custom_proxies(): + """Use external proxy servers for custom routing or specific proxy requirements.""" + # Credentials from CUSTOM_PROXY_SERVER, CUSTOM_PROXY_USERNAME, CUSTOM_PROXY_PASSWORD. + proxy_server = os.environ.get("CUSTOM_PROXY_SERVER") + proxy_username = os.environ.get("CUSTOM_PROXY_USERNAME") + proxy_password = os.environ.get("CUSTOM_PROXY_PASSWORD") + + if not proxy_server or not proxy_username or not proxy_password: + raise ValueError( + "Custom proxy requires CUSTOM_PROXY_SERVER, CUSTOM_PROXY_USERNAME, " + "and CUSTOM_PROXY_PASSWORD environment variables" + ) + + session = bb.sessions.create( + project_id=BROWSERBASE_PROJECT_ID, + proxies=[ + { + "type": "external", # Connect to your own proxy server infrastructure. + "server": proxy_server, + "username": proxy_username, + "password": proxy_password, + } + ], + ) + return session + + +async def test_session_browserbase(session_function, session_name: str): + """Test a Browserbase session with a specific proxy configuration.""" + print(f"\n=== Testing {session_name} ===") + + # Create session with specific proxy configuration to test different routing scenarios. + session = session_function() + print(f"Session URL: https://browserbase.com/sessions/{session.id}") + + # Connect to browser via CDP to control the session programmatically. + async with async_playwright() as p: + browser = await p.chromium.connect_over_cdp(session.connect_url) + default_context = browser.contexts[0] if browser.contexts else None + + if not default_context: + raise RuntimeError("No default context found") + + page = default_context.pages[0] if default_context.pages else None + + if not page: + raise RuntimeError("No page found in default context") + + try: + # Navigate to IP info service to verify proxy location and IP address. + await page.goto("https://ipinfo.io/json", wait_until="domcontentloaded") + + # Parse JSON from page body (pure Playwright; no Stagehand). + body_text = await page.text_content("body") + + if not body_text: + raise RuntimeError("Failed to get page content") + + try: + geo_data = json.loads(body_text) + except json.JSONDecodeError as parse_error: + raise RuntimeError(f"Failed to parse JSON response: {parse_error}") + + print("Geo Info:", json.dumps(geo_data, indent=2)) + + except Exception as error: + print(f"Error during extraction: {error}") + + # Close browser to release resources and end the test session. + await browser.close() + print(f"{session_name} test completed") + + +async def main(): + print("Browserbase Proxy Testing with Playwright") + print("=========================================") + print("This template demonstrates proxy features with Playwright and Browserbase SDK.") + print("It uses pure Playwright + Browserbase SDK.\n") + + # Test 1: Built-in proxies - Verify default proxy rotation works and shows different IPs. + await test_session_browserbase(create_session_with_built_in_proxies, "Built-in Proxies") + + # Test 2: Geolocation proxies - Confirm traffic routes through specified location (New York). + await test_session_browserbase( + create_session_with_geo_location, "Geolocation Proxies (New York)" + ) + + # Test 3: Custom external proxies - Enable if you have CUSTOM_PROXY_* env vars set. + # await test_session_browserbase(create_session_with_custom_proxies, "Custom External Proxies") + + print("\n=== All tests completed ===") + + +if __name__ == "__main__": + try: + asyncio.run(main()) + except Exception as err: + print(f"Application error: {err}") + print("\nCommon issues:") + print(" - Check .env file has BROWSERBASE_PROJECT_ID and BROWSERBASE_API_KEY") + print(" - Verify your Browserbase plan supports proxies") + print("Docs: https://docs.browserbase.com/features/proxies") + exit(1) diff --git a/python/playwright/proxies/pyproject.toml b/python/playwright/proxies/pyproject.toml new file mode 100644 index 00000000..49852a39 --- /dev/null +++ b/python/playwright/proxies/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "playwright-proxies" +version = "1.0.0" +description = "Browserbase + Playwright: Proxy testing (built-in, geolocation, custom external)" +requires-python = ">=3.10" +dependencies = [ + "browserbase", + "playwright", + "python-dotenv", +] + +[project.scripts] +start = "python:main" diff --git a/typescript/playwright/proxies/README.md b/typescript/playwright/proxies/README.md new file mode 100644 index 00000000..fd253e86 --- /dev/null +++ b/typescript/playwright/proxies/README.md @@ -0,0 +1,53 @@ +# Browserbase Proxy Testing with Playwright + +## AT A GLANCE + +- Goal: demonstrate different proxy configurations with Browserbase sessions using pure Playwright (no Stagehand). + +## GLOSSARY + +- Proxies: Browserbase's default proxy rotation for enhanced privacy + Docs → https://docs.browserbase.com/features/proxies + +## QUICKSTART + +1. `cd typescript/playwright/proxies` then `pnpm install` (or `npm install`) +2. Add `.env` in `typescript/` with `BROWSERBASE_API_KEY` and `BROWSERBASE_PROJECT_ID` (script loads `../../.env` from this folder) +3. `pnpm start` (or `npx tsx index.ts`) + +## EXPECTED OUTPUT + +- Tests built-in proxy rotation +- Tests geolocation-specific proxies (New York) +- Tests custom external proxies (commented out by default; requires `CUSTOM_PROXY_SERVER`, `CUSTOM_PROXY_USERNAME`, `CUSTOM_PROXY_PASSWORD`) +- Displays IP information and geolocation data for each test +- Shows how different proxy configurations affect your apparent location + +## COMMON PITFALLS + +- Browserbase Developer plan or higher is required to use proxies +- "Cannot find module": ensure all dependencies are installed at project root (`@browserbasehq/sdk`, `playwright-core`, `dotenv`) +- Missing credentials: verify `.env` contains `BROWSERBASE_PROJECT_ID` and `BROWSERBASE_API_KEY` +- Custom proxy errors: verify external proxy server credentials and availability + +## USE CASES + +• Geo-testing: Verify location-specific content, pricing, or compliance banners. +• Scraping at scale: Rotate IPs to reduce blocks and increase CAPTCHA success rates. +• Custom routing: Mix built-in and external proxies, or apply domain-based rules for compliance. + +## NEXT STEPS + +• Add routing rules: Configure domainPattern to direct specific sites through targeted proxies. +• Test multiple geos: Compare responses from different cities/countries and log differences. +• Improve reliability: Add retries and fallbacks to handle proxy errors like ERR_TUNNEL_CONNECTION_FAILED. + +## HELPFUL RESOURCES + +📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction +📚 Playwright Docs: https://playwright.dev/python/docs/intro +🎮 Browserbase: https://www.browserbase.com +💡 Try it out: https://www.browserbase.com/playground +🔧 Templates: https://www.browserbase.com/templates +📧 Need help? support@browserbase.com +💬 Discord: http://stagehand.dev/discord diff --git a/typescript/playwright/proxies/index.ts b/typescript/playwright/proxies/index.ts new file mode 100644 index 00000000..160e6b33 --- /dev/null +++ b/typescript/playwright/proxies/index.ts @@ -0,0 +1,168 @@ +// Browserbase Proxy Testing with Playwright - See README.md for full documentation + +import { chromium } from "playwright-core"; +import { Browserbase } from "@browserbasehq/sdk"; +import dotenv from "dotenv"; + +dotenv.config(); + +/** IP and geolocation data from ipinfo.io */ +interface GeoInfo { + ip?: string; + city?: string; + region?: string; + country?: string; + loc?: string; + timezone?: string; + org?: string; + postal?: string; + hostname?: string; +} + +const BROWSERBASE_API_KEY = process.env.BROWSERBASE_API_KEY; +const BROWSERBASE_PROJECT_ID = process.env.BROWSERBASE_PROJECT_ID; + +if (!BROWSERBASE_API_KEY) { + throw new Error("BROWSERBASE_API_KEY environment variable is required"); +} +if (!BROWSERBASE_PROJECT_ID) { + throw new Error("BROWSERBASE_PROJECT_ID environment variable is required"); +} + +const bb = new Browserbase({ apiKey: BROWSERBASE_API_KEY }); + +async function createSessionWithBuiltInProxies() { + // Use Browserbase's default proxy rotation for enhanced privacy and IP diversity. + const session = await bb.sessions.create({ + projectId: BROWSERBASE_PROJECT_ID!, + proxies: true, // Enables automatic proxy rotation across different IP addresses. + }); + return session; +} + +async function createSessionWithGeoLocation() { + // Route traffic through specific geographic location to test location-based restrictions. + const session = await bb.sessions.create({ + projectId: BROWSERBASE_PROJECT_ID!, + proxies: [ + { + type: "browserbase", // Use Browserbase's managed proxy infrastructure. + geolocation: { + city: "NEW_YORK", // Simulate traffic from New York for testing geo-specific content. + state: "NY", // See https://docs.browserbase.com/features/proxies for more geolocation options. + country: "US", + }, + }, + ], + }); + return session; +} + +async function createSessionWithCustomProxies() { + // Use external proxy servers for custom routing or specific proxy requirements. + // Credentials from CUSTOM_PROXY_SERVER, CUSTOM_PROXY_USERNAME, CUSTOM_PROXY_PASSWORD. + const proxyServer = process.env.CUSTOM_PROXY_SERVER; + const proxyUsername = process.env.CUSTOM_PROXY_USERNAME; + const proxyPassword = process.env.CUSTOM_PROXY_PASSWORD; + + if (!proxyServer || !proxyUsername || !proxyPassword) { + throw new Error( + "Custom proxy requires CUSTOM_PROXY_SERVER, CUSTOM_PROXY_USERNAME, and CUSTOM_PROXY_PASSWORD environment variables" + ); + } + + const session = await bb.sessions.create({ + projectId: BROWSERBASE_PROJECT_ID!, + proxies: [ + { + type: "external", // Connect to your own proxy server infrastructure. + server: proxyServer!, + username: proxyUsername!, + password: proxyPassword!, + }, + ], + }); + return session; +} + +async function testSessionBrowserbase( + sessionFunction: () => Promise<{ id: string; connectUrl: string }>, + sessionName: string +) { + console.log(`\n=== Testing ${sessionName} ===`); + + // Create session with specific proxy configuration to test different routing scenarios. + const session = await sessionFunction(); + console.log("Session URL: https://browserbase.com/sessions/" + session.id); + + // Connect to browser via CDP to control the session programmatically. + const browser = await chromium.connectOverCDP(session.connectUrl); + const defaultContext = browser.contexts()[0]; + if (!defaultContext) { + throw new Error("No default context found"); + } + const page = defaultContext.pages()[0]; + if (!page) { + throw new Error("No page found in default context"); + } + + try { + // Navigate to IP info service to verify proxy location and IP address. + await page.goto("https://ipinfo.io/json", { + waitUntil: "domcontentloaded", + }); + + // Parse JSON from page body. + const bodyText = await page.textContent("body"); + if (!bodyText) { + throw new Error("Failed to get page content"); + } + + let geoInfo: GeoInfo; + try { + geoInfo = JSON.parse(bodyText); + } catch (parseError) { + throw new Error( + `Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : parseError}` + ); + } + + console.log("Geo Info:", JSON.stringify(geoInfo, null, 2)); + } catch (error) { + console.error( + "Error during extraction:", + error instanceof Error ? error.message : error + ); + } + + // Close browser to release resources and end the test session. + await browser.close(); + console.log(`${sessionName} test completed`); +} + +async function main() { + console.log("Browserbase Proxy Testing with Playwright"); + console.log("========================================="); + console.log("This template demonstrates proxy features with Playwright and Browserbase SDK."); + console.log("It uses pure Playwright + Browserbase SDK.\n"); + + // Test 1: Built-in proxies - Verify default proxy rotation works and shows different IPs. + await testSessionBrowserbase(createSessionWithBuiltInProxies, "Built-in Proxies"); + + // Test 2: Geolocation proxies - Confirm traffic routes through specified location (New York). + await testSessionBrowserbase(createSessionWithGeoLocation, "Geolocation Proxies (New York)"); + + // Test 3: Custom external proxies - Enable if you have CUSTOM_PROXY_* env vars set. + // await testSessionBrowserbase(createSessionWithCustomProxies, "Custom External Proxies"); + + console.log("\n=== All tests completed ==="); +} + +main().catch((err) => { + console.error("Application error:", err); + console.error("\nCommon issues:"); + console.error(" - Check .env file has BROWSERBASE_PROJECT_ID and BROWSERBASE_API_KEY"); + console.error(" - Verify your Browserbase plan supports proxies"); + console.error("Docs: https://docs.browserbase.com/features/proxies"); + process.exit(1); +}); \ No newline at end of file diff --git a/typescript/playwright/proxies/package.json b/typescript/playwright/proxies/package.json new file mode 100644 index 00000000..6f216fb5 --- /dev/null +++ b/typescript/playwright/proxies/package.json @@ -0,0 +1,21 @@ +{ + "name": "playwright-proxies", + "version": "1.0.0", + "description": "Browserbase + Playwright: Proxy testing (built-in, geolocation, custom external)", + "type": "module", + "main": "index.ts", + "scripts": { + "start": "tsx index.ts" + }, + "dependencies": { + "@browserbasehq/sdk": "^2.6.0", + "dotenv": "^17.2.3", + "playwright-core": "^1.57.0" + }, + "devDependencies": { + "@types/node": "^20.14.0", + "tsx": "^4.16.0", + "typescript": "^5.5.0" + }, + "packageManager": "pnpm@9.0.0" +}