Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions packages/e2e/setup/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,14 +318,21 @@ export async function configLink(
// ---------------------------------------------------------------------------

/** Search dev dashboard for an app by name. Returns the app URL or null. */
export async function findAppOnDevDashboard(page: Page, appName: string, orgId?: string): Promise<string | null> {
export async function findAppOnDevDashboard(
page: Page,
appName: string,
orgId?: string,
options: {timeoutMs?: number} = {},
): Promise<string | null> {
const org = orgId ?? (process.env.E2E_ORG_ID ?? '').trim()
const email = process.env.E2E_ACCOUNT_EMAIL
const deadline = options.timeoutMs ? Date.now() + options.timeoutMs : undefined

await navigateToDashboard({browserPage: page, email, orgId: org})

// Scan current page + pagination for the app
while (true) {
if (deadline && Date.now() > deadline) return null
const allLinks = await page.locator('a[href*="/apps/"]').all()
for (const link of allLinks) {
const text = (await link.textContent()) ?? ''
Expand All @@ -337,7 +344,8 @@ export async function findAppOnDevDashboard(page: Page, appName: string, orgId?:

// Check for next page
const nextLink = page.locator('a[href*="next_cursor"]').first()
if (!(await isVisibleWithin(nextLink, BROWSER_TIMEOUT.medium))) break
const remainingMs = deadline ? Math.max(deadline - Date.now(), 0) : BROWSER_TIMEOUT.medium
if (!(await isVisibleWithin(nextLink, Math.min(BROWSER_TIMEOUT.medium, remainingMs)))) break
const nextHref = await nextLink.getAttribute('href')
if (!nextHref) break
const nextUrl = nextHref.startsWith('http') ? nextHref : `https://dev.shopify.com${nextHref}`
Expand Down
34 changes: 32 additions & 2 deletions packages/e2e/setup/teardown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {uninstallAppFromStore, deleteStore, isStoreAppsEmpty, dismissDevConsole}
import type {Page} from '@playwright/test'

const log = createLogger('browser')
const APP_DELETE_TEARDOWN_TIMEOUT_MS = 90_000

interface TeardownCtx {
browserPage: Page
Expand All @@ -17,6 +18,10 @@ interface TeardownCtx {
storeFqdn?: string
}

function isBrowserClosedError(error: unknown): boolean {
return error instanceof Error && error.message.includes('Target page, context or browser has been closed')
}

/**
* Best-effort per-test teardown. Each phase retries up to 3 times.
*
Expand Down Expand Up @@ -122,13 +127,33 @@ export async function teardownAll(ctx: TeardownCtx): Promise<void> {
log.log(wCtx, 'deleting app')
let appDeleted = false
let stillHasInstalls = false
let browserClosed = false
let timedOut = false
const deadline = Date.now() + APP_DELETE_TEARDOWN_TIMEOUT_MS
for (let attempt = 1; attempt <= 3; attempt++) {
const remainingMs = deadline - Date.now()
if (remainingMs <= 0) {
log.log(wCtx, 'app delete skipped — teardown time budget exhausted, run `pnpm test:e2e-cleanup-apps` after')
timedOut = true
break
}
if (page.isClosed()) {
log.log(wCtx, 'app delete skipped — browser page is closed')
browserClosed = true
break
}

try {
const appUrl = await findAppOnDevDashboard(page, ctx.appName, ctx.orgId)
const appUrl = await findAppOnDevDashboard(page, ctx.appName, ctx.orgId, {timeoutMs: remainingMs})
if (!appUrl) {
// null could mean "app not in the list" OR "pagination ended on a stuck error page"
// — findAppOnDevDashboard's refresh-on-error doesn't cover every iteration.
// Detect and retry so we don't misclassify an error page as "already deleted".
if (Date.now() >= deadline) {
log.log(wCtx, 'app delete skipped — teardown time budget exhausted, run `pnpm test:e2e-cleanup-apps` after')
timedOut = true
break
}
if (await refreshIfPageError(page)) {
log.log(wCtx, `page error, refreshing...`)
continue
Expand All @@ -154,10 +179,15 @@ export async function teardownAll(ctx: TeardownCtx): Promise<void> {
stillHasInstalls = true
break
}
if (isBrowserClosedError(err)) {
log.log(wCtx, 'app delete skipped — browser page is closed')
browserClosed = true
break
}
log.log(wCtx, `(${attempt}/3) app deletion failed: ${err instanceof Error ? err.message : err}`)
}
}
if (!appDeleted && !stillHasInstalls) {
if (!appDeleted && !stillHasInstalls && !browserClosed && !timedOut) {
log.error(wCtx, 'app deletion failed after 3 attempts')
}
}
Loading