Skip to content

Commit 174d175

Browse files
committed
fix(browser): improve upload reliability with dedicated profile and better selectors
- Use dedicated Edge profile to avoid WebView2 conflicts - Enable extensions with ignoreDefaultArgs for BitWarden support - Add login detection with 2-minute wait timeout - Improve scroll/click logic to find Social preview section - Use more robust file input selector
1 parent 771ba43 commit 174d175

1 file changed

Lines changed: 141 additions & 59 deletions

File tree

src/browser.ts

Lines changed: 141 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,25 @@ function getEdgePath(): string {
1818
}
1919

2020
function getEdgeUserDataDir(): string {
21+
// Use a dedicated profile directory to avoid WebView2 conflicts
22+
// Extensions need to be installed once in this profile
2123
if (process.platform === 'win32') {
2224
return path.join(
2325
os.homedir(),
2426
'AppData',
2527
'Local',
26-
'Microsoft',
27-
'Edge',
28-
'User Data'
28+
'GH-SocialPreviewGenerator',
29+
'EdgeProfile'
2930
)
3031
} else if (process.platform === 'darwin') {
3132
return path.join(
3233
os.homedir(),
3334
'Library',
3435
'Application Support',
35-
'Microsoft Edge'
36+
'GH-SocialPreviewGenerator'
3637
)
3738
} else {
38-
return path.join(os.homedir(), '.config', 'microsoft-edge')
39+
return path.join(os.homedir(), '.config', 'gh-social-preview-generator')
3940
}
4041
}
4142

@@ -47,9 +48,10 @@ export async function uploadSocialPreviewViaBrowser(
4748
const browser = await puppeteer.launch({
4849
executablePath: getEdgePath(),
4950
userDataDir: getEdgeUserDataDir(),
50-
headless: false, // Need to be visible for user to see progress
51+
headless: false,
5152
defaultViewport: null,
52-
args: ['--no-first-run', '--no-default-browser-check']
53+
args: ['--no-first-run', '--no-default-browser-check'],
54+
ignoreDefaultArgs: ['--disable-extensions']
5355
})
5456

5557
try {
@@ -59,40 +61,85 @@ export async function uploadSocialPreviewViaBrowser(
5961
const settingsUrl = `https://github.com/${owner}/${repo}/settings`
6062
await page.goto(settingsUrl, { waitUntil: 'networkidle2' })
6163

62-
// Wait for the page to load and find the social preview section
63-
// The "Edit" button is in the Social preview section
64-
await page.waitForSelector('details-dialog input[type="file"]', {
65-
timeout: 10000
64+
// Check if we're logged in by looking for the settings form
65+
const settingsForm = await page.$('form[data-turbo="false"], main h2')
66+
if (!settingsForm) {
67+
console.log('\n Please log into GitHub in the browser window...')
68+
console.log(' (Waiting up to 2 minutes for login)\n')
69+
await page.waitForSelector('form[data-turbo="false"], main h2', {
70+
timeout: 120000
71+
})
72+
}
73+
74+
await delay(1000)
75+
76+
// Scroll down to find Social preview section
77+
await page.evaluate(() => {
78+
const labels = document.querySelectorAll('label, h3, summary')
79+
for (const label of labels) {
80+
if (label.textContent?.includes('Social preview')) {
81+
label.scrollIntoView({ behavior: 'smooth', block: 'center' })
82+
break
83+
}
84+
}
6685
})
6786

68-
// Find and click the Edit button for social preview
69-
const editButton = await page.$('button[aria-label="Edit social preview"]')
70-
if (editButton) {
71-
await editButton.click()
72-
await page.waitForSelector('input[type="file"]', {
73-
visible: true,
74-
timeout: 5000
75-
})
87+
await delay(1000)
88+
89+
// Click the Edit button or summary to expand the social preview section
90+
const editClicked = await page.evaluate(() => {
91+
const summaries = document.querySelectorAll('summary, button')
92+
for (const el of summaries) {
93+
if (el.textContent?.includes('Edit') && el.closest('[class*="social"]')) {
94+
;(el as HTMLElement).click()
95+
return true
96+
}
97+
}
98+
// Try clicking any Edit button near Social preview text
99+
const labels = document.querySelectorAll('label, h3')
100+
for (const label of labels) {
101+
if (label.textContent?.includes('Social preview')) {
102+
const parent = label.closest('div')
103+
const btn = parent?.querySelector('summary, button')
104+
if (btn) {
105+
;(btn as HTMLElement).click()
106+
return true
107+
}
108+
}
109+
}
110+
return false
111+
})
112+
113+
if (editClicked) {
114+
await delay(1000)
115+
}
116+
117+
// Find the file input (might be hidden, use a more general selector)
118+
let fileInput = await page.$('input[type="file"]')
119+
if (!fileInput) {
120+
// Wait a bit more for it to appear
121+
await page.waitForSelector('input[type="file"]', { timeout: 5000 })
122+
fileInput = await page.$('input[type="file"]')
76123
}
77124

78-
// Upload the file
79-
const fileInput = await page.$('input[type="file"]')
80125
if (!fileInput) {
81126
throw new Error('Could not find file input for social preview upload')
82127
}
83128

84129
await fileInput.uploadFile(imagePath)
130+
await delay(3000)
85131

86-
// Wait for upload to process
87-
await delay(2000)
88-
89-
// Click the Save/Update button
90-
const saveButton = await page.$(
91-
'button[type="submit"]:has-text("Save"), button:has-text("Update social preview")'
92-
)
93-
if (saveButton) {
94-
await saveButton.click()
95-
await delay(2000)
132+
// Look for and click save button
133+
const buttons = await page.$$('button[type="submit"], button')
134+
for (const button of buttons) {
135+
const text = await button.evaluate(
136+
el => el.textContent?.toLowerCase() || ''
137+
)
138+
if (text.includes('save') || text.includes('update')) {
139+
await button.click()
140+
await delay(2000)
141+
break
142+
}
96143
}
97144

98145
await page.close()
@@ -110,7 +157,8 @@ export async function uploadAllViaBrowser(
110157
userDataDir: getEdgeUserDataDir(),
111158
headless: false,
112159
defaultViewport: null,
113-
args: ['--no-first-run', '--no-default-browser-check']
160+
args: ['--no-first-run', '--no-default-browser-check'],
161+
ignoreDefaultArgs: ['--disable-extensions']
114162
})
115163

116164
let success = 0
@@ -134,57 +182,91 @@ export async function uploadAllViaBrowser(
134182
// Look for the social preview edit button or file input
135183
// GitHub's settings page has a "Social preview" section with an Edit button
136184

137-
// Wait for page to be ready
138-
await page.waitForSelector('main', { timeout: 10000 })
185+
// Check if we're logged in by looking for the settings form
186+
const settingsForm = await page.$('form[data-turbo="false"], main h2')
187+
if (!settingsForm) {
188+
console.log('\n Please log into GitHub in the browser window...')
189+
console.log(' (Waiting up to 2 minutes for login)\n')
190+
await page.waitForSelector('form[data-turbo="false"], main h2', {
191+
timeout: 120000
192+
})
193+
}
194+
195+
await delay(1000)
139196

140-
// Scroll to social preview section if needed
197+
// Scroll down to find Social preview section
141198
await page.evaluate(() => {
142-
const heading = Array.from(
143-
document.querySelectorAll('h2, h3, label')
144-
).find(el => el.textContent?.includes('Social preview'))
145-
if (heading) {
146-
heading.scrollIntoView({ behavior: 'smooth', block: 'center' })
199+
const labels = document.querySelectorAll('label, h3, summary')
200+
for (const label of labels) {
201+
if (label.textContent?.includes('Social preview')) {
202+
label.scrollIntoView({ behavior: 'smooth', block: 'center' })
203+
break
204+
}
147205
}
148206
})
149207

150-
await delay(500)
208+
await delay(1000)
151209

152-
// Click Edit button if there's a summary/details element
153-
const editButton = await page.$(
154-
'button[aria-label="Edit repository image"]'
155-
)
156-
if (editButton) {
157-
await editButton.click()
158-
await delay(500)
210+
// Click the Edit button or summary to expand the social preview section
211+
const editClicked = await page.evaluate(() => {
212+
const summaries = document.querySelectorAll('summary, button')
213+
for (const el of summaries) {
214+
if (
215+
el.textContent?.includes('Edit') &&
216+
el.closest('[class*="social"]')
217+
) {
218+
;(el as HTMLElement).click()
219+
return true
220+
}
221+
}
222+
// Try clicking any Edit button near Social preview text
223+
const labels = document.querySelectorAll('label, h3')
224+
for (const label of labels) {
225+
if (label.textContent?.includes('Social preview')) {
226+
const parent = label.closest('div')
227+
const btn = parent?.querySelector('summary, button')
228+
if (btn) {
229+
;(btn as HTMLElement).click()
230+
return true
231+
}
232+
}
233+
}
234+
return false
235+
})
236+
237+
if (editClicked) {
238+
await delay(1000)
159239
}
160240

161-
// Find the file input (might be hidden)
162-
const fileInput = await page.$('input[type="file"][accept*="image"]')
241+
// Find the file input (might be hidden, use a more general selector)
242+
let fileInput = await page.$('input[type="file"]')
163243
if (!fileInput) {
164-
throw new Error('Could not find file input')
244+
// Wait a bit more for it to appear
245+
await page.waitForSelector('input[type="file"]', { timeout: 5000 })
246+
fileInput = await page.$('input[type="file"]')
165247
}
166248

167-
// Upload the file
168-
await fileInput.uploadFile(imagePath)
249+
if (!fileInput) {
250+
throw new Error('Could not find file input for social preview upload')
251+
}
169252

170-
// Wait for upload to complete
253+
await fileInput.uploadFile(imagePath)
171254
await delay(3000)
172255

173-
// Look for and click save/submit button
174-
const buttons = await page.$$(
175-
'button[type="submit"], button[type="button"]'
176-
)
256+
// Look for and click save button
257+
const buttons = await page.$$('button[type="submit"], button')
177258
for (const button of buttons) {
178259
const text = await button.evaluate(
179260
el => el.textContent?.toLowerCase() || ''
180261
)
181262
if (text.includes('save') || text.includes('update')) {
182263
await button.click()
264+
await delay(2000)
183265
break
184266
}
185267
}
186268

187-
await delay(2000)
269+
await delay(1000)
188270
success++
189271
} catch (error) {
190272
console.error(

0 commit comments

Comments
 (0)