Skip to content

Commit 9abfa0c

Browse files
committed
fix: auto-install browser dependencies in GitHub Codespaces
- Add automatic detection of Codespaces environment - Implement auto-fix using 'sudo apt --fix-broken install' when browser launch fails - Add retry logic after fixing dependencies - Create devcontainer configuration for seamless Codespaces setup - Add comprehensive tests for new functionality Fixes #7990
1 parent 9d33c10 commit 9abfa0c

File tree

6 files changed

+461
-46
lines changed

6 files changed

+461
-46
lines changed

.devcontainer/devcontainer.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "Roo Code Development",
3+
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye",
4+
5+
// Features to add to the dev container
6+
"features": {
7+
"ghcr.io/devcontainers/features/github-cli:1": {}
8+
},
9+
10+
// Install Chromium and its dependencies automatically
11+
"postCreateCommand": "sudo apt-get update && sudo apt --fix-broken install -y && sudo apt-get install -y chromium-browser chromium-codecs-ffmpeg chromium-codecs-ffmpeg-extra libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 libcups2 libdbus-1-3 libdrm2 libgbm1 libgtk-3-0 libnspr4 libnss3 libx11-xcb1 libxcomposite1 libxdamage1 libxfixes3 libxkbcommon0 libxrandr2 xdg-utils && npm install",
12+
13+
// Configure VS Code settings
14+
"customizations": {
15+
"vscode": {
16+
"extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "ms-vscode.vscode-typescript-next"],
17+
"settings": {
18+
"terminal.integrated.defaultProfile.linux": "bash",
19+
"typescript.tsdk": "node_modules/typescript/lib"
20+
}
21+
}
22+
},
23+
24+
// Forward ports for development servers
25+
"forwardPorts": [3000, 5173, 8080],
26+
27+
// Environment variables
28+
"remoteEnv": {
29+
"PUPPETEER_SKIP_CHROMIUM_DOWNLOAD": "true",
30+
"PUPPETEER_EXECUTABLE_PATH": "/usr/bin/chromium-browser",
31+
"CODESPACES": "true"
32+
},
33+
34+
// Run as non-root user
35+
"remoteUser": "node"
36+
}

src/services/browser/BrowserSession.ts

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import delay from "delay"
99
import { fileExistsAtPath } from "../../utils/fs"
1010
import { BrowserActionResult } from "../../shared/ExtensionMessage"
1111
import { discoverChromeHostUrl, tryChromeHostUrl } from "./browserDiscovery"
12+
import { isCodespacesEnvironment, fixCodespaceDependencies, isMissingDependencyError } from "./codespaceUtils"
1213

1314
// Timeout constants
1415
const BROWSER_NAVIGATION_TIMEOUT = 15_000 // 15 seconds
@@ -42,13 +43,35 @@ export class BrowserSession {
4243
await fs.mkdir(puppeteerDir, { recursive: true })
4344
}
4445

45-
// if chromium doesn't exist, this will download it to path.join(puppeteerDir, ".chromium-browser-snapshots")
46-
// if it does exist it will return the path to existing chromium
47-
const stats: PCRStats = await PCR({
48-
downloadPath: puppeteerDir,
49-
})
46+
try {
47+
// if chromium doesn't exist, this will download it to path.join(puppeteerDir, ".chromium-browser-snapshots")
48+
// if it does exist it will return the path to existing chromium
49+
const stats: PCRStats = await PCR({
50+
downloadPath: puppeteerDir,
51+
})
5052

51-
return stats
53+
return stats
54+
} catch (error) {
55+
// Check if this is a missing dependency error in Codespaces
56+
if (isCodespacesEnvironment() && isMissingDependencyError(error)) {
57+
console.log("Detected missing browser dependencies in Codespaces, attempting to fix...")
58+
59+
// Try to fix the dependencies
60+
const fixed = await fixCodespaceDependencies()
61+
62+
if (fixed) {
63+
// Retry PCR after fixing dependencies
64+
console.log("Dependencies fixed, retrying browser initialization...")
65+
const stats: PCRStats = await PCR({
66+
downloadPath: puppeteerDir,
67+
})
68+
return stats
69+
}
70+
}
71+
72+
// If we couldn't fix it or it's not a Codespaces issue, throw the original error
73+
throw error
74+
}
5275
}
5376

5477
/**
@@ -65,16 +88,60 @@ export class BrowserSession {
6588
*/
6689
private async launchLocalBrowser(): Promise<void> {
6790
console.log("Launching local browser")
68-
const stats = await this.ensureChromiumExists()
69-
this.browser = await stats.puppeteer.launch({
70-
args: [
91+
92+
try {
93+
const stats = await this.ensureChromiumExists()
94+
95+
const args = [
7196
"--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
72-
],
73-
executablePath: stats.executablePath,
74-
defaultViewport: this.getViewport(),
75-
// headless: false,
76-
})
77-
this.isUsingRemoteBrowser = false
97+
]
98+
99+
// Add additional args for Linux/Codespaces environments
100+
if (process.platform === "linux" || isCodespacesEnvironment()) {
101+
args.push("--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage")
102+
}
103+
104+
this.browser = await stats.puppeteer.launch({
105+
args,
106+
executablePath: stats.executablePath,
107+
defaultViewport: this.getViewport(),
108+
// headless: false,
109+
})
110+
this.isUsingRemoteBrowser = false
111+
} catch (error) {
112+
// Check if this is a missing dependency error in Codespaces
113+
if (isCodespacesEnvironment() && isMissingDependencyError(error)) {
114+
console.log("Browser launch failed due to missing dependencies, attempting to fix...")
115+
116+
// Try to fix the dependencies
117+
const fixed = await fixCodespaceDependencies()
118+
119+
if (fixed) {
120+
// Retry launching after fixing dependencies
121+
console.log("Dependencies fixed, retrying browser launch...")
122+
const stats = await this.ensureChromiumExists()
123+
124+
const args = [
125+
"--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
126+
"--no-sandbox",
127+
"--disable-setuid-sandbox",
128+
"--disable-dev-shm-usage",
129+
]
130+
131+
this.browser = await stats.puppeteer.launch({
132+
args,
133+
executablePath: stats.executablePath,
134+
defaultViewport: this.getViewport(),
135+
// headless: false,
136+
})
137+
this.isUsingRemoteBrowser = false
138+
return
139+
}
140+
}
141+
142+
// If we couldn't fix it or it's not a Codespaces issue, throw the original error
143+
throw error
144+
}
78145
}
79146

80147
/**

src/services/browser/UrlContentFetcher.ts

Lines changed: 103 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import TurndownService from "turndown"
88
import PCR from "puppeteer-chromium-resolver"
99
import { fileExistsAtPath } from "../../utils/fs"
1010
import { serializeError } from "serialize-error"
11+
import { isCodespacesEnvironment, fixCodespaceDependencies, isMissingDependencyError } from "./codespaceUtils"
1112

1213
// Timeout constants
1314
const URL_FETCH_TIMEOUT = 30_000 // 30 seconds
@@ -37,44 +38,115 @@ export class UrlContentFetcher {
3738
if (!dirExists) {
3839
await fs.mkdir(puppeteerDir, { recursive: true })
3940
}
40-
// if chromium doesn't exist, this will download it to path.join(puppeteerDir, ".chromium-browser-snapshots")
41-
// if it does exist it will return the path to existing chromium
42-
const stats: PCRStats = await PCR({
43-
downloadPath: puppeteerDir,
44-
})
45-
return stats
41+
42+
try {
43+
// if chromium doesn't exist, this will download it to path.join(puppeteerDir, ".chromium-browser-snapshots")
44+
// if it does exist it will return the path to existing chromium
45+
const stats: PCRStats = await PCR({
46+
downloadPath: puppeteerDir,
47+
})
48+
return stats
49+
} catch (error) {
50+
// Check if this is a missing dependency error in Codespaces
51+
if (isCodespacesEnvironment() && isMissingDependencyError(error)) {
52+
console.log("Detected missing browser dependencies in Codespaces, attempting to fix...")
53+
54+
// Try to fix the dependencies
55+
const fixed = await fixCodespaceDependencies()
56+
57+
if (fixed) {
58+
// Retry PCR after fixing dependencies
59+
console.log("Dependencies fixed, retrying browser initialization...")
60+
const stats: PCRStats = await PCR({
61+
downloadPath: puppeteerDir,
62+
})
63+
return stats
64+
}
65+
}
66+
67+
// If we couldn't fix it or it's not a Codespaces issue, throw the original error
68+
throw error
69+
}
4670
}
4771

4872
async launchBrowser(): Promise<void> {
4973
if (this.browser) {
5074
return
5175
}
52-
const stats = await this.ensureChromiumExists()
53-
const args = [
54-
"--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
55-
"--disable-dev-shm-usage",
56-
"--disable-accelerated-2d-canvas",
57-
"--no-first-run",
58-
"--disable-gpu",
59-
"--disable-features=VizDisplayCompositor",
60-
]
61-
if (process.platform === "linux") {
62-
// Fixes network errors on Linux hosts (see https://github.com/puppeteer/puppeteer/issues/8246)
63-
args.push("--no-sandbox")
64-
}
65-
this.browser = await stats.puppeteer.launch({
66-
args,
67-
executablePath: stats.executablePath,
68-
})
69-
// (latest version of puppeteer does not add headless to user agent)
70-
this.page = await this.browser?.newPage()
71-
72-
// Set additional page configurations to improve loading success
73-
if (this.page) {
74-
await this.page.setViewport({ width: 1280, height: 720 })
75-
await this.page.setExtraHTTPHeaders({
76-
"Accept-Language": "en-US,en;q=0.9",
76+
77+
try {
78+
const stats = await this.ensureChromiumExists()
79+
const args = [
80+
"--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
81+
"--disable-dev-shm-usage",
82+
"--disable-accelerated-2d-canvas",
83+
"--no-first-run",
84+
"--disable-gpu",
85+
"--disable-features=VizDisplayCompositor",
86+
]
87+
88+
// Add additional args for Linux/Codespaces environments
89+
if (process.platform === "linux" || isCodespacesEnvironment()) {
90+
args.push("--no-sandbox", "--disable-setuid-sandbox")
91+
}
92+
93+
this.browser = await stats.puppeteer.launch({
94+
args,
95+
executablePath: stats.executablePath,
7796
})
97+
// (latest version of puppeteer does not add headless to user agent)
98+
this.page = await this.browser?.newPage()
99+
100+
// Set additional page configurations to improve loading success
101+
if (this.page) {
102+
await this.page.setViewport({ width: 1280, height: 720 })
103+
await this.page.setExtraHTTPHeaders({
104+
"Accept-Language": "en-US,en;q=0.9",
105+
})
106+
}
107+
} catch (error) {
108+
// Check if this is a missing dependency error in Codespaces
109+
if (isCodespacesEnvironment() && isMissingDependencyError(error)) {
110+
console.log("Browser launch failed due to missing dependencies, attempting to fix...")
111+
112+
// Try to fix the dependencies
113+
const fixed = await fixCodespaceDependencies()
114+
115+
if (fixed) {
116+
// Retry launching after fixing dependencies
117+
console.log("Dependencies fixed, retrying browser launch...")
118+
const stats = await this.ensureChromiumExists()
119+
const args = [
120+
"--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
121+
"--disable-dev-shm-usage",
122+
"--disable-accelerated-2d-canvas",
123+
"--no-first-run",
124+
"--disable-gpu",
125+
"--disable-features=VizDisplayCompositor",
126+
"--no-sandbox",
127+
"--disable-setuid-sandbox",
128+
]
129+
130+
this.browser = await stats.puppeteer.launch({
131+
args,
132+
executablePath: stats.executablePath,
133+
})
134+
// (latest version of puppeteer does not add headless to user agent)
135+
this.page = await this.browser?.newPage()
136+
137+
// Set additional page configurations to improve loading success
138+
if (this.page) {
139+
await this.page.setViewport({ width: 1280, height: 720 })
140+
await this.page.setExtraHTTPHeaders({
141+
"Accept-Language": "en-US,en;q=0.9",
142+
})
143+
}
144+
return
145+
}
146+
}
147+
148+
// If we couldn't fix it or it's not a Codespaces issue, throw the original error
149+
throw error
78150
}
79151
}
80152

src/services/browser/__tests__/UrlContentFetcher.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ describe("UrlContentFetcher", () => {
184184
"--disable-gpu",
185185
"--disable-features=VizDisplayCompositor",
186186
"--no-sandbox", // Linux-specific argument
187+
"--disable-setuid-sandbox", // Additional Linux/Codespaces argument
187188
],
188189
executablePath: "/path/to/chromium",
189190
})

0 commit comments

Comments
 (0)