11/* eslint-disable no-restricted-imports, no-await-in-loop */
22import { authFixture } from './auth.js'
3+ import { navigateToDashboard } from './browser.js'
34import * as path from 'path'
45import * as fs from 'fs'
5- import type { CLIProcess , ExecResult } from './cli.js'
6- import type { Page } from '@playwright/test'
7-
8- // ---------------------------------------------------------------------------
9- // Shared context types
10- // ---------------------------------------------------------------------------
11-
12- export interface CLIContext {
13- cli : CLIProcess
14- appDir : string
15- }
6+ import type { CLIContext , CLIProcess , ExecResult } from './cli.js'
7+ import type { BrowserContext } from './browser.js'
168
179// Env override applied to all CLI helpers — strips CLIENT_ID so commands use the app's own toml.
1810// NOTE: Do NOT add SHOPIFY_CLI_PARTNERS_TOKEN here. The partners token overrides OAuth in the
19- // CLI's auth priority, and its App Management API exchange can't create apps. OAuth handles everything.
11+ // CLI's auth priority, and the App Management API token it exchanges to lacks permissions to
12+ // create apps (403). OAuth provides the full set of required permissions.
2013const FRESH_APP_ENV = { SHOPIFY_FLAG_CLIENT_ID : undefined }
2114
22- export interface BrowserContext {
23- browserPage : Page
24- }
25-
2615// ---------------------------------------------------------------------------
2716// CLI helpers — thin wrappers around cli.exec()
2817// ---------------------------------------------------------------------------
@@ -176,40 +165,9 @@ export async function configLink(
176165}
177166
178167// ---------------------------------------------------------------------------
179- // Browser helpers — use Playwright for actions without CLI commands
168+ // Browser helpers — app-specific dashboard automation
180169// ---------------------------------------------------------------------------
181170
182- /** Navigate to the dev dashboard for the configured org. */
183- export async function navigateToDashboard (
184- ctx : BrowserContext & {
185- email ?: string
186- orgId ?: string
187- } ,
188- ) : Promise < void > {
189- const { browserPage} = ctx
190- const orgId = ctx . orgId ?? ( process . env . E2E_ORG_ID ?? '' ) . split ( '#' ) [ 0 ] ! . trim ( )
191- const dashboardUrl = orgId ? `https://dev.shopify.com/dashboard/${ orgId } /apps` : 'https://dev.shopify.com/dashboard'
192- await browserPage . goto ( dashboardUrl , { waitUntil : 'domcontentloaded' } )
193- await browserPage . waitForTimeout ( 3000 )
194-
195- // Handle account picker (skip if email not provided)
196- if ( ctx . email ) {
197- const accountButton = browserPage . locator ( `text=${ ctx . email } ` ) . first ( )
198- if ( await accountButton . isVisible ( { timeout : 5000 } ) . catch ( ( ) => false ) ) {
199- await accountButton . click ( )
200- await browserPage . waitForTimeout ( 3000 )
201- }
202- }
203-
204- // Retry on 500 errors
205- for ( let attempt = 1 ; attempt <= 3 ; attempt ++ ) {
206- await browserPage . waitForTimeout ( 3000 )
207- const pageText = ( await browserPage . textContent ( 'body' ) ) ?? ''
208- if ( ! pageText . includes ( '500' ) && ! pageText . includes ( 'Internal Server Error' ) ) break
209- await browserPage . reload ( { waitUntil : 'domcontentloaded' } )
210- }
211- }
212-
213171/** Find apps matching a name pattern on the dashboard. Call navigateToDashboard first. */
214172export async function findAppsOnDashboard (
215173 ctx : BrowserContext & {
@@ -224,7 +182,7 @@ export async function findAppsOnDashboard(
224182 const text = await card . textContent ( )
225183 if ( ! href || ! text || ! href . match ( / \/ a p p s \/ \d + / ) ) continue
226184
227- const name = text . split ( / \d + i n s t a l l / ) [ 0 ] ?. trim ( ) ?? text . split ( '\n' ) [ 0 ] ?. trim ( ) ?? text . trim ( )
185+ const name = text . split ( / \d + \s + i n s t a l l / i ) [ 0 ] ?. trim ( ) ?? text . split ( '\n' ) [ 0 ] ?. trim ( ) ?? text . trim ( )
228186 if ( ! name || name . length > 200 ) continue
229187 if ( ! name . includes ( ctx . namePattern ) ) continue
230188
@@ -244,7 +202,7 @@ export async function uninstallApp(
244202 } ,
245203) : Promise < boolean > {
246204 const { browserPage, appUrl, appName} = ctx
247- const orgId = ctx . orgId ?? ( process . env . E2E_ORG_ID ?? '' ) . split ( '#' ) [ 0 ] ! . trim ( )
205+ const orgId = ctx . orgId ?? ( process . env . E2E_ORG_ID ?? '' ) . split ( '#' ) [ 0 ] ? .trim ( ) ?? ''
248206
249207 await browserPage . goto ( `${ appUrl } /installs` , { waitUntil : 'domcontentloaded' } )
250208 await browserPage . waitForTimeout ( 3000 )
@@ -391,13 +349,21 @@ export async function cleanupApp(
391349 await uninstallApp ( { browserPage : ctx . browserPage , appUrl : app . url , appName : app . name , orgId : ctx . orgId } )
392350 await deleteApp ( { browserPage : ctx . browserPage , appUrl : app . url } )
393351 // eslint-disable-next-line no-catch-all/no-catch-all
394- } catch ( _err ) {
352+ } catch ( err ) {
395353 // Best-effort per app — continue cleaning up remaining apps
354+ if ( process . env . DEBUG === '1' ) {
355+ const msg = err instanceof Error ? err . message : String ( err )
356+ process . stderr . write ( `[e2e] Cleanup failed for app ${ app . name } : ${ msg } \n` )
357+ }
396358 }
397359 }
398360 // eslint-disable-next-line no-catch-all/no-catch-all
399- } catch ( _err ) {
361+ } catch ( err ) {
400362 // Best-effort — don't fail the test if cleanup fails
363+ if ( process . env . DEBUG === '1' ) {
364+ const msg = err instanceof Error ? err . message : String ( err )
365+ process . stderr . write ( `[e2e] Cleanup failed for ${ ctx . appName } : ${ msg } \n` )
366+ }
401367 }
402368}
403369
0 commit comments