@@ -125,26 +125,29 @@ export async function createDevStore(
125125// Store admin browser actions — uninstall apps and delete stores
126126// ---------------------------------------------------------------------------
127127
128+ /** Dismiss the Dev Console panel if visible on a store admin page. */
129+ export async function dismissDevConsole ( page : Page ) : Promise < void > {
130+ const devConsole = page . locator ( 'h2:has-text("Dev Console")' )
131+ if ( ! ( await devConsole . isVisible ( { timeout : BROWSER_TIMEOUT . medium } ) . catch ( ( ) => false ) ) ) return
132+
133+ const hideBtn = page . locator ( 'button[aria-label="hide"]' ) . first ( )
134+ if ( await hideBtn . isVisible ( { timeout : BROWSER_TIMEOUT . short } ) . catch ( ( ) => false ) ) {
135+ await hideBtn . click ( )
136+ await page . waitForTimeout ( BROWSER_TIMEOUT . short )
137+ }
138+ }
139+
128140/**
129141 * Uninstall an app from a store's admin settings page.
130142 * Navigates to /settings/apps, finds the app by name, uninstalls it, and verifies removal.
131143 * Returns true if app is confirmed gone, false if still present.
132144 */
133- export async function uninstallAppFromStoreAdmin ( page : Page , storeSlug : string , appName : string ) : Promise < boolean > {
145+ export async function uninstallAppFromStore ( page : Page , storeSlug : string , appName : string ) : Promise < boolean > {
134146 await page . goto ( `https://admin.shopify.com/store/${ storeSlug } /settings/apps` , {
135147 waitUntil : 'domcontentloaded' ,
136148 } )
137149 await page . waitForTimeout ( BROWSER_TIMEOUT . long )
138-
139- // Dismiss any Dev Console dialog
140- const cancelBtn = page . locator ( 'button:has-text("Cancel")' )
141- if ( await cancelBtn . isVisible ( { timeout : BROWSER_TIMEOUT . medium } ) . catch ( ( ) => false ) ) {
142- await cancelBtn . click ( )
143- await page . waitForTimeout ( BROWSER_TIMEOUT . short )
144- }
145-
146- // Check if already uninstalled
147- if ( await isAppsPageEmpty ( page ) ) return true
150+ await dismissDevConsole ( page )
148151
149152 const appSpan = page . locator ( `span:has-text("${ appName } "):not([class*="Polaris"])` ) . first ( )
150153 if ( ! ( await appSpan . isVisible ( { timeout : BROWSER_TIMEOUT . long } ) . catch ( ( ) => false ) ) ) return true
@@ -164,23 +167,24 @@ export async function uninstallAppFromStoreAdmin(page: Page, storeSlug: string,
164167 await page . waitForTimeout ( BROWSER_TIMEOUT . medium )
165168 }
166169
167- // Verify: reload and check app is gone
168- await page . reload ( { waitUntil : 'domcontentloaded' } )
169- await page . waitForTimeout ( BROWSER_TIMEOUT . long )
170+ // Verify: check the specific app is gone
171+ const check = async ( ) =>
172+ page
173+ . locator ( `span:has-text("${ appName } "):not([class*="Polaris"])` )
174+ . first ( )
175+ . isVisible ( { timeout : BROWSER_TIMEOUT . medium } )
176+ . catch ( ( ) => false )
170177
171- if ( await isAppsPageEmpty ( page ) ) return true
178+ if ( ! ( await check ( ) ) ) return true
172179
173- // App name no longer visible = success even if other apps remain
174- const stillVisible = await page
175- . locator ( `span:has-text("${ appName } "):not([class*="Polaris"])` )
176- . first ( )
177- . isVisible ( { timeout : BROWSER_TIMEOUT . medium } )
178- . catch ( ( ) => false )
179- return ! stillVisible
180+ // If still visible — reload and check again
181+ await page . reload ( { waitUntil : 'domcontentloaded' } )
182+ await page . waitForTimeout ( BROWSER_TIMEOUT . long )
183+ return ! ( await check ( ) )
180184}
181185
182- /** Check if the store apps page shows the empty state (zero apps installed). */
183- async function isAppsPageEmpty ( page : Page ) : Promise < boolean > {
186+ /** Check if the current page shows the empty state (zero apps installed). Caller must navigate first . */
187+ export async function isStoreAppsEmpty ( page : Page ) : Promise < boolean > {
184188 // "Add apps to your store" empty state is the definitive zero-apps signal
185189 const emptyState = page . locator ( 'text=Add apps to your store' )
186190 if ( await emptyState . isVisible ( { timeout : BROWSER_TIMEOUT . medium } ) . catch ( ( ) => false ) ) return true
@@ -191,29 +195,11 @@ async function isAppsPageEmpty(page: Page): Promise<boolean> {
191195}
192196
193197/**
194- * Delete a store from the admin settings plan page.
195- * Verifies no apps are installed first — refuses to delete if apps remain .
196- * Returns true if deleted, false if skipped .
198+ * Delete a store via the admin settings plan page.
199+ * Caller must verify no apps are installed before calling .
200+ * Returns true if deleted, false if not .
197201 */
198- export async function deleteStoreFromAdmin ( page : Page , storeSlug : string ) : Promise < boolean > {
199- // Verify no apps are installed before deleting
200- await page . goto ( `https://admin.shopify.com/store/${ storeSlug } /settings/apps` , {
201- waitUntil : 'domcontentloaded' ,
202- } )
203- await page . waitForTimeout ( BROWSER_TIMEOUT . long )
204-
205- const cancelBtn = page . locator ( 'button:has-text("Cancel")' )
206- if ( await cancelBtn . isVisible ( { timeout : BROWSER_TIMEOUT . medium } ) . catch ( ( ) => false ) ) {
207- await cancelBtn . click ( )
208- await page . waitForTimeout ( BROWSER_TIMEOUT . short )
209- }
210-
211- if ( ! ( await isAppsPageEmpty ( page ) ) ) {
212- // Apps still installed — refuse to delete
213- return false
214- }
215-
216- // Delete the store
202+ export async function deleteStore ( page : Page , storeSlug : string ) : Promise < boolean > {
217203 // Step 1: Navigate to plan page and click delete button to open modal (retry navigation on failure)
218204 const planUrl = `https://admin.shopify.com/store/${ storeSlug } /settings/plan`
219205 const deleteButton = page . locator ( 's-internal-button[tone="critical"]' ) . locator ( 'button' )
@@ -222,12 +208,13 @@ export async function deleteStoreFromAdmin(page: Page, storeSlug: string): Promi
222208 try {
223209 await page . goto ( planUrl , { waitUntil : 'domcontentloaded' } )
224210 await page . waitForTimeout ( BROWSER_TIMEOUT . long )
211+ // If redirected to access_account, store is already deleted
212+ if ( page . url ( ) . includes ( 'access_account' ) ) return true
225213 await deleteButton . click ( { timeout : BROWSER_TIMEOUT . long } )
226214 break
227215 // eslint-disable-next-line no-catch-all/no-catch-all
228216 } catch ( _err ) {
229217 if ( attempt === 3 ) return false
230- await page . waitForTimeout ( BROWSER_TIMEOUT . medium )
231218 }
232219 }
233220 await page . waitForTimeout ( BROWSER_TIMEOUT . medium )
@@ -264,15 +251,24 @@ export async function deleteStoreFromAdmin(page: Page, storeSlug: string): Promi
264251 await page . waitForTimeout ( BROWSER_TIMEOUT . short )
265252 }
266253
267- await confirmButton . click ( { force : true } )
268- await page . waitForURL ( / a c c e s s _ a c c o u n t / , { timeout : BROWSER_TIMEOUT . max } )
269-
270- // Verify: "Your plan was canceled" confirms the store is deleted
271- const canceled = page . locator ( 'text=Your plan was canceled' )
272- const verified = await canceled . isVisible ( { timeout : BROWSER_TIMEOUT . long } ) . catch ( ( ) => false )
273- return verified || page . url ( ) . includes ( 'access_account' )
254+ const confirmClicked = await confirmButton
255+ . click ( { force : true } )
256+ . then ( ( ) => true )
257+ . catch ( ( ) => false )
258+ if ( ! confirmClicked ) return false
259+
260+ // Verify: URL reaching access_account confirms store is deleted
261+ try {
262+ await page . waitForURL ( / a c c e s s _ a c c o u n t / , { timeout : BROWSER_TIMEOUT . max } )
263+ return true
264+ // eslint-disable-next-line no-catch-all/no-catch-all
265+ } catch ( _err ) {
266+ return false
267+ }
274268}
275269
270+
271+
276272// ---------------------------------------------------------------------------
277273// Fixture — per-test dev store for tests that need `app dev`
278274// ---------------------------------------------------------------------------
0 commit comments