Skip to content

Commit cb219bf

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

1 file changed

Lines changed: 64 additions & 39 deletions

File tree

packages/e2e/setup/global-auth.ts

Lines changed: 64 additions & 39 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,34 @@ 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+
// eslint-disable-next-line no-catch-all/no-catch-all
75+
} catch (_error) {
76+
// Browser check failed — fall through to re-authenticate
77+
} finally {
78+
await browser.close().catch(() => {})
79+
}
80+
globalLog('auth', 'cached session expired, re-authenticating')
81+
} else {
82+
globalLog('auth', 'no cached session found')
83+
}
84+
85+
// Create fresh XDG dirs
86+
for (const dir of Object.values(xdgEnv)) {
87+
fs.mkdirSync(dir, {recursive: true})
88+
}
89+
5390
// Clear any existing session
5491
await execa('node', [executables.cli, 'auth', 'logout'], {
5592
env: processEnv,
@@ -78,8 +115,6 @@ export default async function globalSetup() {
78115
if (debug) process.stdout.write(data)
79116
})
80117

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

@@ -107,31 +142,8 @@ export default async function globalSetup() {
107142
// (completeLogin only authenticates on accounts.shopify.com)
108143
const orgId = (process.env.E2E_ORG_ID ?? '').trim()
109144
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-
145+
await visitAndHandleAccountPicker(page, 'https://admin.shopify.com/', email)
146+
await visitAndHandleAccountPicker(page, `https://dev.shopify.com/dashboard/${orgId}/apps`, email)
135147
globalLog('auth', 'browser sessions established for admin + dev dashboard')
136148
}
137149

@@ -149,14 +161,27 @@ export default async function globalSetup() {
149161
}
150162
}
151163

152-
// Store paths so workers can copy CLI auth + load browser state
153-
/* eslint-disable require-atomic-updates */
164+
setAuthEnvVars(xdgEnv, storageStatePath)
165+
globalLog('auth', `global setup done, config at ${xdgEnv.XDG_CONFIG_HOME}`)
166+
}
167+
168+
/** Navigate to a URL and dismiss the account picker if it appears. */
169+
async function visitAndHandleAccountPicker(page: Page, url: string, email: string) {
170+
await page.goto(url, {waitUntil: 'domcontentloaded'})
171+
await page.waitForTimeout(BROWSER_TIMEOUT.medium)
172+
if (isAccountsShopifyUrl(page.url())) {
173+
const accountButton = page.locator(`text=${email}`).first()
174+
if (await accountButton.isVisible({timeout: BROWSER_TIMEOUT.long}).catch(() => false)) {
175+
await accountButton.click()
176+
await page.waitForTimeout(BROWSER_TIMEOUT.medium)
177+
}
178+
}
179+
}
180+
181+
function setAuthEnvVars(xdgEnv: Record<string, string>, storageStatePath: string): void {
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
159-
/* eslint-enable require-atomic-updates */
160-
161-
globalLog('auth', `global setup done, config at ${xdgEnv.XDG_CONFIG_HOME}`)
162187
}

0 commit comments

Comments
 (0)