diff --git a/examples/contexts/README.MD b/examples/contexts/README.MD new file mode 100644 index 0000000..d698512 --- /dev/null +++ b/examples/contexts/README.MD @@ -0,0 +1,26 @@ +# Browserbase Context Persistence Demo + +This project demonstrates how to use Browserbase's context persistence feature to maintain browser state across multiple sessions. + +## Overview + +The demo script showcases: +- Creating a Browserbase context +- Starting a browser session with a persistent context +- Setting and verifying cookie persistence between pages +- Creating a new session with the same context +- Verifying that the browser state (cookies) persists between sessions + +## Prerequisites + +- Node.js (v14 or higher recommended) +- A Browserbase account with API access +- Your Browserbase Project ID and API Key + +## Installation + +1. Clone this repository +2. Install dependencies: npm install +3. Create a `.env` file and add your Browserbase Project ID and API Key +4. Run: tsc -w +5. Run: node dist/index.js \ No newline at end of file diff --git a/examples/contexts/index.ts b/examples/contexts/index.ts new file mode 100644 index 0000000..00df589 --- /dev/null +++ b/examples/contexts/index.ts @@ -0,0 +1,159 @@ +import Browserbase from "@browserbasehq/sdk"; +import { chromium, type Browser, type Cookie } from "playwright-core"; +import dotenv from "dotenv"; + +dotenv.config(); + +// Configuration +const CONTEXT_TEST_URL = "https://www.browserbase.com"; +const BROWSERBASE_PROJECT_ID = process.env["BROWSERBASE_PROJECT_ID"]!; +const BROWSERBASE_API_KEY = process.env["BROWSERBASE_API_KEY"]!; + +const bb = new Browserbase({ + apiKey: BROWSERBASE_API_KEY, +}); + +// Helper functions +function addHour(date: Date): number { + const SECOND = 1000; + return new Date(date.getTime() + 60 * 60 * 1000).getTime() / SECOND; +} + +async function findCookie( + browser: Browser, + name: string +): Promise { + const [defaultContext] = browser.contexts(); + const cookies = await defaultContext?.cookies(); + console.log("Cookies:", cookies); + return cookies?.find((cookie) => cookie.name === name); +} + +async function runSessionWithContextPersistence() { + let contextId: string; + let sessionId: string; + let testCookieName: string; + let testCookieValue: string; + + try { + // Step 1: Create a context + console.log("Creating a new context..."); + const context = await bb.contexts.create({ + projectId: BROWSERBASE_PROJECT_ID, + }); + contextId = context.id; + console.log(`Context created with context ID: ${contextId}`); + + // Step 2: Create a session with the context + console.log("Creating a session with the context..."); + const session = await bb.sessions.create({ + projectId: BROWSERBASE_PROJECT_ID, + browserSettings: { + context: { + id: contextId, + persist: true, + }, + }, + }); + sessionId = session.id; + console.log(`Session created with session ID: ${sessionId}`); + + // Step 3: Populate and persist the context + console.log(`Creating browser and navigating to ${CONTEXT_TEST_URL}`); + const browser = await chromium.connectOverCDP(session.connectUrl); + const browserContext = browser.contexts()[0]!; + const page = browserContext.pages()[0]!; + + await page.goto(CONTEXT_TEST_URL, { waitUntil: "domcontentloaded" }); + + // Set a random cookie on the page + console.log("Adding cookies..."); + const now = new Date(); + testCookieName = `bb_${now.getTime().toString()}`; + testCookieValue = now.toISOString(); + + await browserContext.addCookies([ + { + domain: ".browserbase.com", + expires: addHour(now), + name: testCookieName, + path: "/", + value: testCookieValue, + }, + ]); + + console.log("-"); + console.log(`Set test cookie: ${testCookieName}=${testCookieValue}`); + console.log("-"); + + // Validate cookie persistence between pages + console.log("Navigating to Google and back to check cookie persistence..."); + await page.goto("https://www.google.com", { + waitUntil: "domcontentloaded", + timeout: 60000, + }); + await page.goBack(); + + const cookie = await findCookie(browser, testCookieName); + console.log("-"); + console.log("Cookie persisted between pages:", !!cookie); + console.log("-"); + + await page.close(); + await browser.close(); + console.log("Closing first session..."); + + // Wait for context to persist + const persistTimeout = 5000; + console.log(`Waiting ${persistTimeout}ms for context to persist...`); + await new Promise((resolve) => setTimeout(resolve, persistTimeout)); + + // Step 4: Create another session with the same context + console.log(`Creating a new session with the same context ID: ${contextId}...`); + const newSession = await bb.sessions.create({ + projectId: BROWSERBASE_PROJECT_ID, + browserSettings: { + context: { + id: contextId, + persist: false, + }, + }, + }); + const newSessionId = newSession.id; + console.log(`Session created with session ID: ${newSessionId}`); + + // Step 5: Verify previous state + console.log(`Creating browser and navigating to ${CONTEXT_TEST_URL}`); + const newBrowser = await chromium.connectOverCDP(newSession.connectUrl); + const newPage = newBrowser.contexts()[0]!.pages()[0]!; + + await newPage.goto(CONTEXT_TEST_URL, { waitUntil: "domcontentloaded" }); + + const foundCookie = await findCookie(newBrowser, testCookieName); + console.log("-"); + console.log("Cookie found in new session:", !!foundCookie); + console.log("-"); + console.log( + "Cookie value matches:", + foundCookie?.value === testCookieValue + ); + console.log("-"); + + console.log("Closing second session..."); + await newPage.close(); + await newBrowser.close(); + + console.log("Context persistence test completed successfully!"); + console.log( + `View session replays at:\n https://browserbase.com/sessions/${sessionId}\n https://browserbase.com/sessions/${newSessionId}` + ); + } catch (error) { + console.error("An error occurred:", error); + process.exit(1); + } +} + +// Run the script +console.log("Running script..."); +await runSessionWithContextPersistence(); +console.log("Script completed!"); \ No newline at end of file diff --git a/examples/contexts/package.json b/examples/contexts/package.json new file mode 100644 index 0000000..5553f17 --- /dev/null +++ b/examples/contexts/package.json @@ -0,0 +1,20 @@ +{ + "name": "contexts-playwright", + "version": "1.0.0", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "@browserbasehq/sdk": "^2.0.0", + "dotenv": "^16.4.7", + "playwright-core": "^1.43.1" + }, + "devDependencies": { + "@types/node": "^20.12.7" + } +} diff --git a/examples/contexts/placeholder.txt b/examples/contexts/placeholder.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/contexts/placeholder.txt @@ -0,0 +1 @@ + diff --git a/examples/contexts/tsconfig.json b/examples/contexts/tsconfig.json new file mode 100644 index 0000000..294d88f --- /dev/null +++ b/examples/contexts/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "nodenext", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "outDir": "dist/" + } + } \ No newline at end of file diff --git a/examples/screenshot/LICENSE b/examples/screenshot/LICENSE new file mode 100644 index 0000000..0168fed --- /dev/null +++ b/examples/screenshot/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Browserbase Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/examples/screenshot/README.md b/examples/screenshot/README.md new file mode 100644 index 0000000..a32e1df --- /dev/null +++ b/examples/screenshot/README.md @@ -0,0 +1,62 @@ +

+ + + Browserbase logo + +

+ +

+ Documentation +  ยท  + Playground +

+
+ +## Playwright with Browserbase +Browserbase is the best developer platform to reliably run, manage, and monitor headless browsers. + +Get browsers' full control and leverage Browserbase's +[Infrastructure](https://docs.browserbase.com/under-the-hood), [Stealth Mode](https://docs.browserbase.com/features/stealth-mode), and +[Session Debugger](https://docs.browserbase.com/features/sessions) to power your automation, test suites, +and LLM data retrievals. + +**Get started in under one minute** with Playwright. + + +## Setup + +### 1. Install dependencies and launch TypeScript in watch mode: + +```bash +npm install +tsc -w +``` + + +### 2. Get your Browserbase API Key and Project ID: + +- [Create an account](https://www.browserbase.com/sign-up) or [log in to Browserbase](https://www.browserbase.com/sign-in) +- Copy your API Key and Project ID [from your Settings page](https://www.browserbase.com/settings) +- Create a `.env` file in the root of the project and add the following variables: + +``` +BROWSERBASE_PROJECT_ID=xxx +BROWSERBASE_API_KEY=xxxx +``` + +### 3. Run the script: + +```bash +node dist/index.js +``` + +### 4. Optional: Run in parallel + +Update the `COUNT_TO_RUN_IN_PARALLEL` variable in the `index.ts` file to have parallel runs for testing. + + +## Further reading + +- [See how to leverage the Session Debugger for faster development](https://docs.browserbase.com/guides/browser-remote-control#accelerate-your-local-development-with-remote-debugging) +- [Learn more about Browserbase infrastructure](https://docs.browserbase.com/under-the-hood) +- [Explore the Sessions API](https://docs.browserbase.com/api-reference/list-all-sessions) \ No newline at end of file diff --git a/examples/screenshot/index.ts b/examples/screenshot/index.ts new file mode 100644 index 0000000..3c1f122 --- /dev/null +++ b/examples/screenshot/index.ts @@ -0,0 +1,86 @@ +import { chromium } from "playwright-core"; +import Browserbase from "@browserbasehq/sdk"; +import dotenv from "dotenv"; +import { writeFileSync } from "fs"; + +dotenv.config(); + +const PROJECT_ID = process.env.BROWSERBASE_PROJECT_ID; +const API_KEY = process.env.BROWSERBASE_API_KEY; + +const URL_TO_TAKE_SCREENSHOT_OF = "https://www.oakley.com/en-us"; + +if (!API_KEY) { + throw new Error("BROWSERBASE_API_KEY is not set"); +} + +if (!PROJECT_ID) { + throw new Error("BROWSERBASE_PROJECT_ID is not set"); +} + +async function main() { + const bb = new Browserbase({ + apiKey: API_KEY, + }); + + const session = await bb.sessions.create({ + projectId: PROJECT_ID as string, + proxies: true, + browserSettings: { + viewport: { + width: 375, + height: 814, + }, + fingerprint: { + devices: ["mobile"], + operatingSystems: ["ios"], + screen: { + minWidth: 375, + maxWidth: 428, + minHeight: 667, + maxHeight: 926, + }, + }, + }, + }); + console.log(`Session created, id: ${session.id}`); + + console.log("Starting remote browser..."); + const browser = await chromium.connectOverCDP(session.connectUrl); + const defaultContext = browser.contexts()[0]; + const page = defaultContext.pages()[0] + + await page.goto(URL_TO_TAKE_SCREENSHOT_OF, { + // let's make sure the page is fully loaded before asking for the live debug URL + waitUntil: "domcontentloaded", + }); + + const debugUrls = await bb.sessions.debug(session.id); + console.log( + `Session started, live debug accessible here: ${debugUrls.debuggerUrl}.` + ); + + console.log("Taking a screenshot!"); + const buf = await page.screenshot({ fullPage: true }); + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + writeFileSync( + `${process.env.HOME}/Downloads/screenshot-${timestamp}.jpeg`, + buf + ); + console.log( + `Screenshot saved to Downloads folder: screenshot-${timestamp}.jpeg` + ); + + console.log("Shutting down..."); + await page.close(); + await browser.close(); + + console.log( + `Session complete! View replay at https://browserbase.com/sessions/${session.id}` + ); +} + +const COUNT_TO_RUN_IN_PARALLEL = 1; +for (let i = 0; i < COUNT_TO_RUN_IN_PARALLEL; i++) { + main(); +} diff --git a/examples/screenshot/package.json b/examples/screenshot/package.json new file mode 100644 index 0000000..7f7efaa --- /dev/null +++ b/examples/screenshot/package.json @@ -0,0 +1,29 @@ +{ + "name": "browserbase-quickstart-playwright", + "version": "0.0.1", + "description": "A template to use Playwright with Browserbase", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/browserbase/quickstart-playwright-js.git" + }, + "private": "true", + "author": "Paul Klein ", + "license": "MIT", + "bugs": { + "url": "https://github.com/browserbase/quickstart-playwright-js/issues" + }, + "homepage": "https://github.com/browserbase/quickstart-playwright-js#readme", + "dependencies": { + "@browserbasehq/sdk": "^2.0.0", + "dotenv": "^16.4.7", + "playwright-core": "^1.43.1" + }, + "devDependencies": { + "@types/node": "^20.12.7" + } +} diff --git a/examples/screenshot/placeholder.txt b/examples/screenshot/placeholder.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/screenshot/placeholder.txt @@ -0,0 +1 @@ + diff --git a/examples/screenshot/tsconfig.json b/examples/screenshot/tsconfig.json new file mode 100644 index 0000000..54a19bf --- /dev/null +++ b/examples/screenshot/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "nodenext", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "outDir": "dist/" + } +}