Skip to content

Commit 2ce70e0

Browse files
Fix E2E cleanup CI checks
1 parent 26e51a9 commit 2ce70e0

3 files changed

Lines changed: 69 additions & 3 deletions

File tree

.github/workflows/tests-pr.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,35 @@ jobs:
219219
if: needs.unit-tests.result != 'success'
220220
run: exit 1
221221

222+
e2e-cleanup-apps:
223+
name: 'E2E preflight app cleanup'
224+
if: github.event_name == 'merge_group' || github.event.pull_request.head.repo.full_name == github.repository
225+
runs-on: ubuntu-latest
226+
timeout-minutes: 10
227+
steps:
228+
- uses: actions/checkout@v6
229+
with:
230+
repository: ${{ github.event.pull_request.head.repo.full_name || github.event.repository.full_name }}
231+
ref: ${{ github.event.pull_request.head.ref || github.event.merge_group.head_ref }}
232+
fetch-depth: 1
233+
- name: Setup deps
234+
uses: ./.github/actions/setup-cli-deps
235+
with:
236+
node-version: ${{ env.PLAYWRIGHT_NODE_VERSION }}
237+
- name: Install Playwright Chromium
238+
run: pnpm exec playwright install chromium
239+
working-directory: packages/e2e
240+
- name: Delete stale E2E apps
241+
working-directory: packages/e2e
242+
env:
243+
E2E_ACCOUNT_EMAIL: ${{ secrets.E2E_ACCOUNT_EMAIL }}
244+
E2E_ACCOUNT_PASSWORD: ${{ secrets.E2E_ACCOUNT_PASSWORD }}
245+
E2E_ORG_ID: ${{ secrets.E2E_ORG_ID }}
246+
run: pnpm exec tsx scripts/cleanup-apps.ts --delete --max-apps 25 --max-pages 10
247+
222248
e2e-tests:
223249
name: "E2E tests (shard ${{ matrix.shard }})"
250+
needs: e2e-cleanup-apps
224251
if: github.event_name == 'merge_group' || github.event.pull_request.head.repo.full_name == github.repository
225252
runs-on: ubuntu-latest
226253
timeout-minutes: 20

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@
147147
"unresolved": "error"
148148
},
149149
"ignoreBinaries": [
150-
"playwright"
150+
"playwright",
151+
"tsx"
151152
],
152153
"ignoreDependencies": [
153154
"@shopify/generate-docs"

packages/e2e/scripts/cleanup-apps.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
* pnpm --filter e2e exec tsx scripts/cleanup-apps.ts --delete # Delete only (skip uninstall — delete only apps with 0 installs)
1414
* pnpm --filter e2e exec tsx scripts/cleanup-apps.ts --headed # Show browser window
1515
* pnpm --filter e2e exec tsx scripts/cleanup-apps.ts --pattern X # Match apps containing "X" (default: "E2E-")
16+
* pnpm --filter e2e exec tsx scripts/cleanup-apps.ts --max-apps 25 # Stop after finding 25 matching apps
17+
* pnpm --filter e2e exec tsx scripts/cleanup-apps.ts --max-pages 5 # Stop after scanning 5 dashboard pages
1618
*
1719
* Environment variables (loaded from packages/e2e/.env):
1820
* E2E_ACCOUNT_EMAIL — Shopify account email for login
@@ -59,6 +61,10 @@ export interface CleanupOptions {
5961
headed?: boolean
6062
/** Organization ID (default: from E2E_ORG_ID env) */
6163
orgId?: string
64+
/** Stop discovering apps after this many matching apps */
65+
maxApps?: number
66+
/** Stop discovering apps after this many dashboard pages */
67+
maxPages?: number
6268
}
6369

6470
/**
@@ -77,6 +83,8 @@ export async function cleanupAllApps(opts: CleanupOptions = {}): Promise<void> {
7783
console.log(`[cleanup-apps] Mode: ${MODE_LABELS[mode]}`)
7884
console.log(`[cleanup-apps] Org: ${orgId || '(not set)'}`)
7985
console.log(`[cleanup-apps] Pattern: "${pattern}"`)
86+
if (opts.maxApps) console.log(`[cleanup-apps] Max apps: ${opts.maxApps}`)
87+
if (opts.maxPages) console.log(`[cleanup-apps] Max pages: ${opts.maxPages}`)
8088
console.log('')
8189

8290
if (!email || !password) {
@@ -118,7 +126,11 @@ export async function cleanupAllApps(opts: CleanupOptions = {}): Promise<void> {
118126

119127
// Step 3: Find matching apps
120128
console.log('[cleanup-apps] Finding matching apps...')
121-
const apps = await findAppsOnDashboard(page, pattern)
129+
const apps = await findAppsOnDashboard(page, pattern, {
130+
maxApps: opts.maxApps,
131+
maxPages: opts.maxPages,
132+
onlyUninstalled: mode === 'delete',
133+
})
122134
console.log(`[cleanup-apps] Found ${apps.length} app(s) matching pattern "${pattern}"`)
123135
console.log('')
124136

@@ -233,12 +245,15 @@ export async function cleanupAllApps(opts: CleanupOptions = {}): Promise<void> {
233245
async function findAppsOnDashboard(
234246
page: Page,
235247
namePattern: string,
248+
opts: {maxApps?: number; maxPages?: number; onlyUninstalled?: boolean} = {},
236249
): Promise<{name: string; url: string; installs: number}[]> {
237250
const apps: {name: string; url: string; installs: number}[] = []
238251
let totalSeen = 0
252+
let pagesSeen = 0
239253

240254
// eslint-disable-next-line no-constant-condition
241255
while (true) {
256+
pagesSeen++
242257
// Recover from transient 500/502 before parsing the page
243258
for (let attempt = 1; attempt <= 3; attempt++) {
244259
if (!(await refreshIfPageError(page))) break
@@ -261,13 +276,18 @@ async function findAppsOnDashboard(
261276

262277
const installMatch = text.match(/(\d+)\s+install/i)
263278
const installs = installMatch ? parseInt(installMatch[1]!, 10) : 0
279+
if (opts.onlyUninstalled && installs > 0) continue
264280

265281
const url = href.startsWith('http') ? href : `https://dev.shopify.com${href}`
266282
apps.push({name, url, installs})
283+
if (opts.maxApps && apps.length >= opts.maxApps) break
267284
}
268285

269286
console.log(`[cleanup-apps] ...loaded ${totalSeen} apps`)
270287

288+
if (opts.maxApps && apps.length >= opts.maxApps) break
289+
if (opts.maxPages && pagesSeen >= opts.maxPages) break
290+
271291
// Check for next page — navigate via href since the button click may not work
272292
const nextLink = page.locator('a[href*="next_cursor"]').first()
273293
if (!(await nextLink.isVisible({timeout: BROWSER_TIMEOUT.medium}).catch(() => false))) break
@@ -392,12 +412,30 @@ async function main() {
392412
pattern = nextArg
393413
}
394414

415+
const maxApps = parsePositiveIntegerOption(args, '--max-apps')
416+
const maxPages = parsePositiveIntegerOption(args, '--max-pages')
417+
395418
let mode: CleanupMode = 'full'
396419
if (args.includes('--list')) mode = 'list'
397420
else if (args.includes('--uninstall')) mode = 'uninstall'
398421
else if (args.includes('--delete')) mode = 'delete'
399422

400-
await cleanupAllApps({mode, pattern, headed})
423+
await cleanupAllApps({mode, pattern, headed, maxApps, maxPages})
424+
}
425+
426+
function parsePositiveIntegerOption(args: string[], name: string): number | undefined {
427+
const optionIndex = args.indexOf(name)
428+
if (optionIndex === -1) return undefined
429+
430+
const optionValue = args[optionIndex + 1]
431+
const parsedValue = Number(optionValue)
432+
if (!optionValue || !Number.isInteger(parsedValue) || parsedValue <= 0) {
433+
console.error(`[cleanup-apps] ${name} requires a positive integer`)
434+
process.exitCode = 1
435+
return undefined
436+
}
437+
438+
return parsedValue
401439
}
402440

403441
// Run if executed directly (not imported)

0 commit comments

Comments
 (0)