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// ---------------------------------------------------------------------------
@@ -177,40 +166,9 @@ export async function configLink(
177166}
178167
179168// ---------------------------------------------------------------------------
180- // Browser helpers — use Playwright for actions without CLI commands
169+ // Browser helpers — app-specific dashboard automation
181170// ---------------------------------------------------------------------------
182171
183- /** Navigate to the dev dashboard for the configured org. */
184- export async function navigateToDashboard (
185- ctx : BrowserContext & {
186- email ?: string
187- orgId ?: string
188- } ,
189- ) : Promise < void > {
190- const { browserPage} = ctx
191- const orgId = ctx . orgId ?? ( process . env . E2E_ORG_ID ?? '' ) . split ( '#' ) [ 0 ] ! . trim ( )
192- const dashboardUrl = orgId ? `https://dev.shopify.com/dashboard/${ orgId } /apps` : 'https://dev.shopify.com/dashboard'
193- await browserPage . goto ( dashboardUrl , { waitUntil : 'domcontentloaded' } )
194- await browserPage . waitForTimeout ( 3000 )
195-
196- // Handle account picker (skip if email not provided)
197- if ( ctx . email ) {
198- const accountButton = browserPage . locator ( `text=${ ctx . email } ` ) . first ( )
199- if ( await accountButton . isVisible ( { timeout : 5000 } ) . catch ( ( ) => false ) ) {
200- await accountButton . click ( )
201- await browserPage . waitForTimeout ( 3000 )
202- }
203- }
204-
205- // Retry on 500 errors
206- for ( let attempt = 1 ; attempt <= 3 ; attempt ++ ) {
207- await browserPage . waitForTimeout ( 3000 )
208- const pageText = ( await browserPage . textContent ( 'body' ) ) ?? ''
209- if ( ! pageText . includes ( '500' ) && ! pageText . includes ( 'Internal Server Error' ) ) break
210- await browserPage . reload ( { waitUntil : 'domcontentloaded' } )
211- }
212- }
213-
214172/** Find apps matching a name pattern on the dashboard. Call navigateToDashboard first. */
215173export async function findAppsOnDashboard (
216174 ctx : BrowserContext & {
@@ -225,7 +183,7 @@ export async function findAppsOnDashboard(
225183 const text = await card . textContent ( )
226184 if ( ! href || ! text || ! href . match ( / \/ a p p s \/ \d + / ) ) continue
227185
228- const name = text . split ( / \d + i n s t a l l / ) [ 0 ] ?. trim ( ) ?? text . split ( '\n' ) [ 0 ] ?. trim ( ) ?? text . trim ( )
186+ const name = text . split ( / \d + \s + i n s t a l l / i ) [ 0 ] ?. trim ( ) ?? text . split ( '\n' ) [ 0 ] ?. trim ( ) ?? text . trim ( )
229187 if ( ! name || name . length > 200 ) continue
230188 if ( ! name . includes ( ctx . namePattern ) ) continue
231189
@@ -245,7 +203,7 @@ export async function uninstallApp(
245203 } ,
246204) : Promise < boolean > {
247205 const { browserPage, appUrl, appName} = ctx
248- const orgId = ctx . orgId ?? ( process . env . E2E_ORG_ID ?? '' ) . split ( '#' ) [ 0 ] ! . trim ( )
206+ const orgId = ctx . orgId ?? ( process . env . E2E_ORG_ID ?? '' ) . trim ( )
249207
250208 await browserPage . goto ( `${ appUrl } /installs` , { waitUntil : 'domcontentloaded' } )
251209 await browserPage . waitForTimeout ( 3000 )
@@ -264,7 +222,9 @@ export async function uninstallApp(
264222 for ( const storeName of storeNames ) {
265223 try {
266224 // Navigate to store admin via the dev dashboard dropdown
267- const dashboardUrl = `https://dev.shopify.com/dashboard/${ orgId } /apps`
225+ const dashboardUrl = orgId
226+ ? `https://dev.shopify.com/dashboard/${ orgId } /apps`
227+ : 'https://dev.shopify.com/dashboard'
268228 let navigated = false
269229 for ( let attempt = 1 ; attempt <= 3 ; attempt ++ ) {
270230 await browserPage . goto ( dashboardUrl , { waitUntil : 'domcontentloaded' } )
@@ -293,7 +253,7 @@ export async function uninstallApp(
293253
294254 // Navigate to store's apps settings page
295255 const storeAdminUrl = browserPage . url ( )
296- await browserPage . goto ( `${ storeAdminUrl . replace ( / \/ $ / , '' ) } /settings/apps` , { waitUntil : 'networkidle ' } )
256+ await browserPage . goto ( `${ storeAdminUrl . replace ( / \/ $ / , '' ) } /settings/apps` , { waitUntil : 'domcontentloaded ' } )
297257 await browserPage . waitForTimeout ( 5000 )
298258
299259 // Dismiss any Dev Console dialog
@@ -392,13 +352,21 @@ export async function cleanupApp(
392352 await uninstallApp ( { browserPage : ctx . browserPage , appUrl : app . url , appName : app . name , orgId : ctx . orgId } )
393353 await deleteApp ( { browserPage : ctx . browserPage , appUrl : app . url } )
394354 // eslint-disable-next-line no-catch-all/no-catch-all
395- } catch ( _err ) {
355+ } catch ( err ) {
396356 // Best-effort per app — continue cleaning up remaining apps
357+ if ( process . env . DEBUG === '1' ) {
358+ const msg = err instanceof Error ? err . message : String ( err )
359+ process . stderr . write ( `[e2e] Cleanup failed for app ${ app . name } : ${ msg } \n` )
360+ }
397361 }
398362 }
399363 // eslint-disable-next-line no-catch-all/no-catch-all
400- } catch ( _err ) {
364+ } catch ( err ) {
401365 // Best-effort — don't fail the test if cleanup fails
366+ if ( process . env . DEBUG === '1' ) {
367+ const msg = err instanceof Error ? err . message : String ( err )
368+ process . stderr . write ( `[e2e] Cleanup failed for ${ ctx . appName } : ${ msg } \n` )
369+ }
402370 }
403371}
404372
0 commit comments