Skip to content

Commit 0c49907

Browse files
committed
add extensions test
1 parent dd6dd1c commit 0c49907

File tree

8 files changed

+239
-2
lines changed

8 files changed

+239
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ codegen.log
1616
Brewfile.lock.json
1717
screenshot.png
1818
openapi.v1.yaml
19+
**/.DS_Store

examples/e2e/test_playwright.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,45 @@
33
from dotenv import load_dotenv
44
import os
55
from browserbase import Browserbase
6+
from typing import Callable, TypeVar, Any
7+
import functools
68

79
from .. import (
810
BROWSERBASE_API_KEY,
911
playwright_basic,
1012
playwright_captcha,
1113
playwright_contexts,
1214
playwright_downloads,
15+
playwright_extensions,
1316
)
1417

1518
bb = Browserbase(api_key=BROWSERBASE_API_KEY)
1619
load_dotenv()
1720

1821
SKIP_CAPTCHA_SOLVING = os.getenv("SKIP_CAPTCHA_SOLVING", "true").lower() == "true"
1922

23+
T = TypeVar("T")
24+
25+
26+
# Decorator to make a test flaky
27+
def flaky(max_attempts: int = 3):
28+
def decorator(func: Callable[..., T]) -> Callable[..., T]:
29+
@functools.wraps(func)
30+
def wrapper(*args: Any, **kwargs: Any) -> T:
31+
for attempt in range(max_attempts):
32+
try:
33+
return func(*args, **kwargs)
34+
except AssertionError:
35+
if attempt == max_attempts - 1:
36+
raise
37+
raise AssertionError(
38+
f"Function {func.__name__} failed after {max_attempts} attempts"
39+
)
40+
41+
return wrapper
42+
43+
return decorator
44+
2045

2146
@pytest.fixture(scope="session")
2247
def playwright():
@@ -28,15 +53,24 @@ def test_playwright_basic(playwright: Playwright):
2853
playwright_basic.run(playwright)
2954

3055

56+
# This test takes a while to run, so we'll skip it unless we specifically want to run it
57+
@pytest.mark.skipif(
58+
SKIP_CAPTCHA_SOLVING,
59+
reason="Skipping captcha solving, it takes a long time and is flaky",
60+
)
3161
def test_playwright_captcha(playwright: Playwright):
32-
if SKIP_CAPTCHA_SOLVING:
33-
pytest.skip("Skipping captcha solving")
3462
playwright_captcha.run(playwright)
3563

3664

3765
def test_playwright_contexts(playwright: Playwright):
3866
playwright_contexts.run(playwright)
3967

4068

69+
@flaky(max_attempts=5)
4170
def test_playwright_downloads(playwright: Playwright):
4271
playwright_downloads.run(playwright)
72+
73+
74+
@flaky(max_attempts=5)
75+
def test_playwright_extensions(playwright: Playwright):
76+
playwright_extensions.run(playwright)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.zip
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<html>
2+
<body>
3+
<h1>Hello Extensions</h1>
4+
</body>
5+
</html>
5.81 KB
Loading
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"manifest_version": 3,
3+
"name": "Browserbase Extension Test",
4+
"description": "Test extension for browserbase",
5+
"version": "1.0",
6+
"action": {
7+
"default_popup": "hello.html"
8+
},
9+
"content_scripts": [
10+
{
11+
"matches": ["https://www.browserbase.com/*"],
12+
"js": ["scripts/content.js"]
13+
}
14+
],
15+
"web_accessible_resources": [
16+
{
17+
"resources": ["images/logo.png"],
18+
"matches": ["https://www.browserbase.com/*"]
19+
}
20+
]
21+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const imageUrl = chrome.runtime.getURL("images/logo.png");
2+
window
3+
.fetch(imageUrl)
4+
.then((response) => {
5+
if (response.ok) {
6+
console.log("browserbase test extension image loaded");
7+
}
8+
})
9+
.catch((error) => {
10+
console.log(error);
11+
});

examples/playwright_extensions.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import os
2+
import zipfile
3+
import time
4+
from pathlib import Path
5+
from playwright.sync_api import sync_playwright, Playwright
6+
from browserbase.types.extension import Extension
7+
from browserbase.types.session import Session
8+
from examples import (
9+
BROWSERBASE_API_KEY,
10+
BROWSERBASE_PROJECT_ID,
11+
BROWSERBASE_CONNECT_URL,
12+
bb,
13+
)
14+
from io import BytesIO
15+
16+
PATH_TO_EXTENSION = (
17+
Path.cwd() / "examples" / "packages" / "extensions" / "browserbase-test"
18+
)
19+
20+
21+
def zip_extension(path: Path = PATH_TO_EXTENSION, save_local: bool = False) -> BytesIO:
22+
"""
23+
Create an in-memory zip file from the contents of the given folder.
24+
Mark save_local=True to save the zip file to a local file.
25+
"""
26+
# Ensure we're looking at an extension
27+
assert "manifest.json" in os.listdir(
28+
path
29+
), "No manifest.json found in the extension folder."
30+
31+
# Create a BytesIO object to hold the zip file in memory
32+
memory_zip = BytesIO()
33+
34+
# Create a ZipFile object
35+
with zipfile.ZipFile(memory_zip, "w", zipfile.ZIP_DEFLATED) as zf:
36+
# Recursively walk through the directory
37+
for root, _, files in os.walk(path):
38+
for file in files:
39+
# Create the full file path
40+
file_path = os.path.join(root, file)
41+
# Calculate the archive name (path relative to the root directory)
42+
archive_name = os.path.relpath(file_path, path)
43+
# Add the file to the zip
44+
zf.write(file_path, archive_name)
45+
46+
if save_local:
47+
with open(f"{path}.zip", "wb") as f:
48+
f.write(memory_zip.getvalue())
49+
50+
return memory_zip
51+
52+
53+
def create_extension():
54+
zip_data = zip_extension(save_local=True)
55+
extension: Extension = bb.extensions.create(
56+
file=("extension.zip", zip_data.getvalue())
57+
)
58+
return extension.id
59+
60+
61+
def get_extension(id: str) -> Extension:
62+
return bb.extensions.retrieve(id)
63+
64+
65+
def delete_extension(id: str):
66+
bb.extensions.delete(id)
67+
68+
69+
def run(playwright: Playwright):
70+
extension_id = None
71+
72+
# Create extension
73+
extension_id = create_extension()
74+
print(f"Created extension with ID: {extension_id}")
75+
76+
# Get extension
77+
extension = get_extension(extension_id)
78+
print(f"Retrieved extension: {extension}")
79+
80+
# Use extension
81+
session: Session = bb.sessions.create(
82+
project_id=BROWSERBASE_PROJECT_ID,
83+
extension_id=extension.id,
84+
)
85+
86+
browser = playwright.chromium.connect_over_cdp(
87+
f"{BROWSERBASE_CONNECT_URL}?apiKey={BROWSERBASE_API_KEY}&sessionId={session.id}"
88+
)
89+
context = browser.contexts[0]
90+
page = context.pages[0]
91+
92+
console_messages: list[str] = []
93+
page.on("console", lambda msg: console_messages.append(msg.text))
94+
95+
page.goto("https://www.browserbase.com/")
96+
97+
# Wait for the extension to load and log a message
98+
start = time.time()
99+
while time.time() - start < 10:
100+
if "browserbase test extension image loaded" in console_messages:
101+
break
102+
assert (
103+
"browserbase test extension image loaded" in console_messages
104+
), f"Expected message not found in console logs. Messages: {console_messages}"
105+
106+
page.close()
107+
browser.close()
108+
109+
# Use extension with proxies
110+
session_with_proxy: Session = bb.sessions.create(
111+
project_id=BROWSERBASE_PROJECT_ID,
112+
extension_id=extension_id,
113+
proxies=True,
114+
)
115+
116+
browser = playwright.chromium.connect_over_cdp(
117+
f"{BROWSERBASE_CONNECT_URL}?apiKey={BROWSERBASE_API_KEY}&sessionId={session_with_proxy.id}"
118+
)
119+
context = browser.contexts[0]
120+
page = context.pages[0]
121+
122+
console_messages: list[str] = []
123+
page.on("console", lambda msg: console_messages.append(msg.text))
124+
125+
page.goto("https://www.browserbase.com/")
126+
127+
# Wait for the extension to load and log a message (longer timeout for proxies)
128+
start = time.time()
129+
while time.time() - start < 10:
130+
if "browserbase test extension image loaded" in console_messages:
131+
break
132+
assert (
133+
"browserbase test extension image loaded" in console_messages
134+
), f"Expected message not found in console logs. Messages: {console_messages}"
135+
136+
page.close()
137+
browser.close()
138+
139+
# Delete extension
140+
delete_extension(extension_id)
141+
print(f"Deleted extension with ID: {extension_id}")
142+
143+
# Verify deleted extension is unusable
144+
try:
145+
get_extension(extension_id)
146+
raise AssertionError("Expected to fail when retrieving deleted extension")
147+
except Exception as e:
148+
print(f"Failed to get deleted extension as expected: {str(e)}")
149+
150+
try:
151+
bb.sessions.create(
152+
project_id=BROWSERBASE_PROJECT_ID,
153+
extension_id=extension_id,
154+
)
155+
raise AssertionError(
156+
"Expected to fail when creating session with deleted extension"
157+
)
158+
except Exception as e:
159+
print(f"Failed to create session with deleted extension as expected: {str(e)}")
160+
161+
162+
if __name__ == "__main__":
163+
with sync_playwright() as playwright:
164+
run(playwright)

0 commit comments

Comments
 (0)