Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
47a7f22 to
1c6838f
Compare
There was a problem hiding this comment.
Pull request overview
Adds a standalone E2E utility script to bulk-discover and clean up leftover dev stores created by failing/aborted E2E runs, using Playwright browser automation against the Dev Dashboard + Store Admin.
Changes:
- Introduces
packages/e2e/scripts/cleanup-stores.tswith--list,--delete, and default “full” cleanup modes. - Implements store discovery via Dev Dashboard pagination and regex extraction of store FQDNs from page HTML.
- Automates app uninstall (with pagination) and store deletion flows with retries and safety checks (empty-state confirmation).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const isDirectRun = process.argv[1] === fileURLToPath(import.meta.url) | ||
| if (isDirectRun) { | ||
| main().catch((err) => { | ||
| console.error('[cleanup-stores] Fatal error:', err) | ||
| process.exitCode = 1 | ||
| }) |
There was a problem hiding this comment.
isDirectRun compares process.argv[1] to fileURLToPath(import.meta.url) using strict string equality. When running via npx tsx packages/e2e/scripts/cleanup-stores.ts, process.argv[1] is typically a relative path, so this check can be false and main() won’t run at all. Consider normalizing both sides (e.g., path.resolve(process.argv[1])) or comparing URLs via pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url.
| /** | ||
| * Delete a store via the admin settings plan page. | ||
| * Caller must ensure all apps are already uninstalled. | ||
| * Retries the full flow if the page redirects to store home instead of access_account. | ||
| */ | ||
| async function deleteStore(page: Page, storeSlug: string): Promise<void> { | ||
| const planUrl = `https://admin.shopify.com/store/${storeSlug}/settings/plan` | ||
|
|
||
| for (let attempt = 1; attempt <= 3; attempt++) { | ||
| try { | ||
| // Step 1: Navigate to plan page (wait for full hydration before clicking) |
There was a problem hiding this comment.
This script defines a local deleteStore() helper that largely overlaps with the existing deleteStore browser helper in packages/e2e/setup/store.ts. Having two different implementations with the same name risks divergence and makes future maintenance/debugging harder. Consider reusing the shared helper (and extending it if needed) or renaming this one to avoid confusion (e.g., deleteStoreViaPlanPage).
1c6838f to
73d2cae
Compare
73d2cae to
de46b8d
Compare

WHY are these changes introduced?
E2E tests create dev stores that can accumulate when tests fail mid-run, CI times out, or teardown fails. This script automates bulk-clean for leftover stores.
WHAT is this pull request doing?
cleanup-stores.tsStandalone cleanup script that finds leftover E2E dev stores, uninstalls their apps, and deletes them via browser automation.
Logic
Discovery phase:
completeLoginhelperdev.shopify.com/dashboard/{orgId}/storespage.content()) — catches slugs in hrefs/attributes, not just visible texte2e-w)a[href*="next_cursor"]and repeat 3–4 on each page--listmode (per store):admin.shopify.com/store/{slug}/settings/appsbutton[aria-label="More actions"](whichever appears first viaPromise.race)button#nextURL)default mode (per store):
admin.shopify.com/store/{slug}/settings/appsPromise.race— either empty state or first app menu buttonadmin.shopify.com/store/{slug}/settings/planwithwaitUntil: 'load'+ 10s fixed wait for shadow DOM hydrationaccess_account→ store already deleted, dones-internal-button[tone="critical"]→button) — retry click up to 3 times without re-navigation (first click often refreshes the page due to hydration).Polaris-Modal-Dialog__Modal:has-text(...))waitForURLforaccess_account(success) vs store home path (failure → retry from step 7, up to 3 outer attempts)--deletemode: same as default but skips stores that have apps installed (step 4 → skip if not empty).Uninstall logic (
uninstallAllAppsOnPage):button[aria-label="More actions"]on the pagediv[role="listitem"]→a spanconsecutiveSkips, try next buttonconsecutiveSkips(prevents infinite loop)button#nextURLpagination → continue on next pageFeatures:
Promise.racebefore checking empty state (avoids false negatives from slow renders)cleanupStores()for use from other scriptsHow is this different from per-test teardown?
setup/teardown.ts) — knows the specific app name and store FQDN, uses direct URLs, no discovery. Runs automatically in testfinallyblocks.cleanup-stores.ts(bulk, manual) — discovers all matching stores via dashboard pagination. Safety net for orphaned stores from failed/interrupted test runs.How to test your changes?
pnpm --filter e2e exec tsx scripts/cleanup-stores.ts --listpnpm --filter e2e exec tsx scripts/cleanup-stores.ts --headedExample:
pnpm --filter e2e exec tsx scripts/cleanup-stores.ts --headedDirect success
cleanup-store-success.mov
Failure → retry → success
cleanup-store-failure.retry.mov
Expand for complete log
Post-release steps
Checklist
patchfor bug fixes ·minorfor new features ·majorfor breaking changes) and added a changeset withpnpm changeset add