Skip to content

Commit 121f09a

Browse files
committed
session caching + consolidate auth dir
1 parent 0e57009 commit 121f09a

1 file changed

Lines changed: 63 additions & 37 deletions

File tree

packages/e2e/setup/global-auth.ts

Lines changed: 63 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
/**
22
* Playwright globalSetup — authenticates once before any workers start.
33
*
4-
* Performs CLI `auth login` with a dedicated temp dir, then stores the
5-
* path in E2E_AUTH_CONFIG_DIR so each worker can copy the session files
6-
* into its own isolated XDG dirs.
4+
* Uses a stable `global-auth/` dir for session caching across runs.
5+
* On subsequent runs, validates the cached browser session before
6+
* re-authenticating. Workers copy the session files into their own
7+
* isolated XDG dirs via E2E_AUTH_* env vars.
78
*/
89

910
/* eslint-disable no-restricted-imports */
10-
import {createIsolatedEnv, directories, executables, globalLog} from './env.js'
11+
import {directories, executables, globalLog} from './env.js'
1112
import {CLI_TIMEOUT, BROWSER_TIMEOUT} from './constants.js'
1213
import {stripAnsi} from '../helpers/strip-ansi.js'
1314
import {waitForText} from '../helpers/wait-for-text.js'
1415
import {completeLogin} from '../helpers/browser-login.js'
1516
import {execa} from 'execa'
16-
import {chromium} from '@playwright/test'
17+
import {chromium, type Page} from '@playwright/test'
1718
import * as path from 'path'
1819
import * as fs from 'fs'
1920

@@ -35,10 +36,18 @@ export default async function globalSetup() {
3536
const debug = process.env.DEBUG === '1'
3637
globalLog('auth', 'global setup starting')
3738

38-
// Create a temp dir for the auth session
39+
// Use a stable auth dir (reused across runs for session caching)
3940
const tmpBase = process.env.E2E_TEMP_DIR ?? path.join(directories.root, '.e2e-tmp')
4041
fs.mkdirSync(tmpBase, {recursive: true})
41-
const {xdgEnv} = createIsolatedEnv(tmpBase)
42+
const authDir = path.join(tmpBase, 'global-auth')
43+
const storageStatePath = path.join(authDir, 'browser-storage-state.json')
44+
45+
const xdgEnv = {
46+
XDG_DATA_HOME: path.join(authDir, 'XDG_DATA_HOME'),
47+
XDG_CONFIG_HOME: path.join(authDir, 'XDG_CONFIG_HOME'),
48+
XDG_STATE_HOME: path.join(authDir, 'XDG_STATE_HOME'),
49+
XDG_CACHE_HOME: path.join(authDir, 'XDG_CACHE_HOME'),
50+
}
4251

4352
const processEnv: NodeJS.ProcessEnv = {
4453
...process.env,
@@ -50,6 +59,33 @@ export default async function globalSetup() {
5059
SHOPIFY_FLAG_CLIENT_ID: undefined,
5160
}
5261

62+
// Check if cached session from a previous run is still valid
63+
if (fs.existsSync(storageStatePath)) {
64+
const browser = await chromium.launch({headless: true})
65+
try {
66+
const context = await browser.newContext({storageState: storageStatePath})
67+
const page = await context.newPage()
68+
await page.goto('https://admin.shopify.com/', {waitUntil: 'domcontentloaded', timeout: BROWSER_TIMEOUT.long})
69+
if (!isAccountsShopifyUrl(page.url())) {
70+
globalLog('auth', 'reusing cached session')
71+
setAuthEnvVars(xdgEnv, storageStatePath)
72+
return
73+
}
74+
} catch {
75+
// Browser check failed — fall through to re-authenticate
76+
} finally {
77+
await browser.close().catch(() => {})
78+
}
79+
globalLog('auth', 'cached session expired, re-authenticating')
80+
} else {
81+
globalLog('auth', 'no cached session found')
82+
}
83+
84+
// Create fresh XDG dirs
85+
for (const dir of Object.values(xdgEnv)) {
86+
fs.mkdirSync(dir, {recursive: true})
87+
}
88+
5389
// Clear any existing session
5490
await execa('node', [executables.cli, 'auth', 'logout'], {
5591
env: processEnv,
@@ -78,8 +114,6 @@ export default async function globalSetup() {
78114
if (debug) process.stdout.write(data)
79115
})
80116

81-
const storageStatePath = path.join(tmpBase, 'browser-storage-state.json')
82-
83117
try {
84118
await waitForText(() => output, 'Open this link to start the auth process', CLI_TIMEOUT.short)
85119

@@ -107,31 +141,8 @@ export default async function globalSetup() {
107141
// (completeLogin only authenticates on accounts.shopify.com)
108142
const orgId = (process.env.E2E_ORG_ID ?? '').trim()
109143
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)
113-
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'})
125-
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-
144+
await visitAndHandleAccountPicker(page, 'https://admin.shopify.com/', email)
145+
await visitAndHandleAccountPicker(page, `https://dev.shopify.com/dashboard/${orgId}/apps`, email)
135146
globalLog('auth', 'browser sessions established for admin + dev dashboard')
136147
}
137148

@@ -149,14 +160,29 @@ export default async function globalSetup() {
149160
}
150161
}
151162

152-
// Store paths so workers can copy CLI auth + load browser state
163+
setAuthEnvVars(xdgEnv, storageStatePath)
164+
globalLog('auth', `global setup done, config at ${xdgEnv.XDG_CONFIG_HOME}`)
165+
}
166+
167+
/** Navigate to a URL and dismiss the account picker if it appears. */
168+
async function visitAndHandleAccountPicker(page: Page, url: string, email: string) {
169+
await page.goto(url, {waitUntil: 'domcontentloaded'})
170+
await page.waitForTimeout(BROWSER_TIMEOUT.medium)
171+
if (isAccountsShopifyUrl(page.url())) {
172+
const accountButton = page.locator(`text=${email}`).first()
173+
if (await accountButton.isVisible({timeout: BROWSER_TIMEOUT.long}).catch(() => false)) {
174+
await accountButton.click()
175+
await page.waitForTimeout(BROWSER_TIMEOUT.medium)
176+
}
177+
}
178+
}
179+
180+
function setAuthEnvVars(xdgEnv: Record<string, string>, storageStatePath: string): void {
153181
/* eslint-disable require-atomic-updates */
154182
process.env.E2E_AUTH_CONFIG_DIR = xdgEnv.XDG_CONFIG_HOME
155183
process.env.E2E_AUTH_DATA_DIR = xdgEnv.XDG_DATA_HOME
156184
process.env.E2E_AUTH_STATE_DIR = xdgEnv.XDG_STATE_HOME
157185
process.env.E2E_AUTH_CACHE_DIR = xdgEnv.XDG_CACHE_HOME
158186
process.env.E2E_BROWSER_STATE_PATH = storageStatePath
159187
/* eslint-enable require-atomic-updates */
160-
161-
globalLog('auth', `global setup done, config at ${xdgEnv.XDG_CONFIG_HOME}`)
162188
}

0 commit comments

Comments
 (0)