Skip to content

Commit 0e57009

Browse files
committed
fix: validate global auth artifacts and ensure resource cleanup
1 parent 54c055d commit 0e57009

3 files changed

Lines changed: 72 additions & 55 deletions

File tree

packages/e2e/setup/auth.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ export const authFixture = browserFixture.extend<{}, {authLogin: void}>({
3939
// Copy pre-authenticated session from global setup
4040
log.log(env, 'copying session from global setup')
4141

42+
if (
43+
!fs.existsSync(authConfigDir) ||
44+
!fs.existsSync(authDataDir) ||
45+
!fs.existsSync(authStateDir) ||
46+
!fs.existsSync(authCacheDir)
47+
) {
48+
throw new Error('Global auth dirs missing — global setup may not have completed successfully')
49+
}
50+
4251
fs.cpSync(authConfigDir, env.processEnv.XDG_CONFIG_HOME!, {recursive: true})
4352
fs.cpSync(authDataDir, env.processEnv.XDG_DATA_HOME!, {recursive: true})
4453
fs.cpSync(authStateDir, env.processEnv.XDG_STATE_HOME!, {recursive: true})

packages/e2e/setup/browser.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {cliFixture} from './cli.js'
22
import {BROWSER_TIMEOUT} from './constants.js'
33
import {chromium, type Page} from '@playwright/test'
4+
import * as fs from 'fs'
45

56
// ---------------------------------------------------------------------------
67
// Shared browser context type
@@ -29,11 +30,12 @@ export const browserFixture = cliFixture.extend<{}, {browserPage: Page}>({
2930
async ({}, use) => {
3031
const browser = await chromium.launch({headless: !process.env.E2E_HEADED})
3132
const storageStatePath = process.env.E2E_BROWSER_STATE_PATH
33+
const hasValidStorageState = storageStatePath && fs.existsSync(storageStatePath)
3234
const context = await browser.newContext({
3335
extraHTTPHeaders: {
3436
'X-Shopify-Loadtest-Bf8d22e7-120e-4b5b-906c-39ca9d5499a9': 'true',
3537
},
36-
...(storageStatePath ? {storageState: storageStatePath} : {}),
38+
...(hasValidStorageState ? {storageState: storageStatePath} : {}),
3739
})
3840
context.setDefaultTimeout(BROWSER_TIMEOUT.max)
3941
context.setDefaultNavigationTimeout(BROWSER_TIMEOUT.max)

packages/e2e/setup/global-auth.ts

Lines changed: 60 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -78,71 +78,77 @@ export default async function globalSetup() {
7878
if (debug) process.stdout.write(data)
7979
})
8080

81-
await waitForText(() => output, 'Open this link to start the auth process', CLI_TIMEOUT.short)
82-
83-
const stripped = stripAnsi(output)
84-
const urlMatch = stripped.match(/https:\/\/accounts\.shopify\.com\S+/)
85-
if (!urlMatch) {
86-
throw new Error(`[e2e] global-auth: could not find login URL in output:\n${stripped}`)
87-
}
88-
89-
// Complete login in a headless browser
90-
const browser = await chromium.launch({headless: !process.env.E2E_HEADED})
91-
const context = await browser.newContext({
92-
extraHTTPHeaders: {
93-
'X-Shopify-Loadtest-Bf8d22e7-120e-4b5b-906c-39ca9d5499a9': 'true',
94-
},
95-
})
96-
const page = await context.newPage()
97-
98-
await completeLogin(page, urlMatch[0], email, password)
81+
const storageStatePath = path.join(tmpBase, 'browser-storage-state.json')
9982

100-
await waitForText(() => output, 'Logged in', BROWSER_TIMEOUT.max)
10183
try {
102-
ptyProcess.kill()
103-
// eslint-disable-next-line no-catch-all/no-catch-all
104-
} catch (_error) {
105-
// Process may already be dead
106-
}
84+
await waitForText(() => output, 'Open this link to start the auth process', CLI_TIMEOUT.short)
10785

108-
// Visit admin.shopify.com and dev.shopify.com to establish session cookies
109-
// (completeLogin only authenticates on accounts.shopify.com)
110-
const orgId = (process.env.E2E_ORG_ID ?? '').trim()
111-
if (orgId) {
112-
// Establish admin.shopify.com cookies
113-
await page.goto('https://admin.shopify.com/', {waitUntil: 'domcontentloaded'})
114-
await page.waitForTimeout(BROWSER_TIMEOUT.medium)
115-
116-
// Handle account picker if shown
117-
if (isAccountsShopifyUrl(page.url())) {
118-
const accountButton = page.locator(`text=${email}`).first()
119-
if (await accountButton.isVisible({timeout: BROWSER_TIMEOUT.long}).catch(() => false)) {
120-
await accountButton.click()
121-
await page.waitForTimeout(BROWSER_TIMEOUT.medium)
122-
}
86+
const stripped = stripAnsi(output)
87+
const urlMatch = stripped.match(/https:\/\/accounts\.shopify\.com\S+/)
88+
if (!urlMatch) {
89+
throw new Error(`[e2e] global-auth: could not find login URL in output:\n${stripped}`)
12390
}
12491

125-
// Establish dev.shopify.com cookies
126-
await page.goto(`https://dev.shopify.com/dashboard/${orgId}/apps`, {waitUntil: 'domcontentloaded'})
127-
await page.waitForTimeout(BROWSER_TIMEOUT.medium)
92+
// Complete login in a headless browser
93+
const browser = await chromium.launch({headless: !process.env.E2E_HEADED})
94+
try {
95+
const context = await browser.newContext({
96+
extraHTTPHeaders: {
97+
'X-Shopify-Loadtest-Bf8d22e7-120e-4b5b-906c-39ca9d5499a9': 'true',
98+
},
99+
})
100+
const page = await context.newPage()
101+
102+
await completeLogin(page, urlMatch[0], email, password)
103+
104+
await waitForText(() => output, 'Logged in', BROWSER_TIMEOUT.max)
105+
106+
// Visit admin.shopify.com and dev.shopify.com to establish session cookies
107+
// (completeLogin only authenticates on accounts.shopify.com)
108+
const orgId = (process.env.E2E_ORG_ID ?? '').trim()
109+
if (orgId) {
110+
// Establish admin.shopify.com cookies
111+
await page.goto('https://admin.shopify.com/', {waitUntil: 'domcontentloaded'})
112+
await page.waitForTimeout(BROWSER_TIMEOUT.medium)
128113

129-
if (isAccountsShopifyUrl(page.url())) {
130-
const accountButton = page.locator(`text=${email}`).first()
131-
if (await accountButton.isVisible({timeout: BROWSER_TIMEOUT.long}).catch(() => false)) {
132-
await accountButton.click()
114+
// Handle account picker if shown
115+
if (isAccountsShopifyUrl(page.url())) {
116+
const accountButton = page.locator(`text=${email}`).first()
117+
if (await accountButton.isVisible({timeout: BROWSER_TIMEOUT.long}).catch(() => false)) {
118+
await accountButton.click()
119+
await page.waitForTimeout(BROWSER_TIMEOUT.medium)
120+
}
121+
}
122+
123+
// Establish dev.shopify.com cookies
124+
await page.goto(`https://dev.shopify.com/dashboard/${orgId}/apps`, {waitUntil: 'domcontentloaded'})
133125
await page.waitForTimeout(BROWSER_TIMEOUT.medium)
126+
127+
if (isAccountsShopifyUrl(page.url())) {
128+
const accountButton = page.locator(`text=${email}`).first()
129+
if (await accountButton.isVisible({timeout: BROWSER_TIMEOUT.long}).catch(() => false)) {
130+
await accountButton.click()
131+
await page.waitForTimeout(BROWSER_TIMEOUT.medium)
132+
}
133+
}
134+
135+
globalLog('auth', 'browser sessions established for admin + dev dashboard')
134136
}
135-
}
136137

137-
globalLog('auth', 'browser sessions established for admin + dev dashboard')
138+
// Save browser cookies/storage so workers can reuse the session
139+
await context.storageState({path: storageStatePath})
140+
} finally {
141+
await browser.close()
142+
}
143+
} finally {
144+
try {
145+
ptyProcess.kill()
146+
// eslint-disable-next-line no-catch-all/no-catch-all
147+
} catch (_error) {
148+
// Process may already be dead
149+
}
138150
}
139151

140-
// Save browser cookies/storage so workers can reuse the session
141-
// Now includes cookies for both accounts.shopify.com AND admin.shopify.com
142-
const storageStatePath = path.join(tmpBase, 'browser-storage-state.json')
143-
await context.storageState({path: storageStatePath})
144-
await browser.close()
145-
146152
// Store paths so workers can copy CLI auth + load browser state
147153
/* eslint-disable require-atomic-updates */
148154
process.env.E2E_AUTH_CONFIG_DIR = xdgEnv.XDG_CONFIG_HOME

0 commit comments

Comments
 (0)