E2E: utility - cleanup stores#7355
E2E: utility - cleanup stores#7355phyllis-sy-wu wants to merge 1 commit intopsyw-0421-E2E-teardown-polishfrom
Conversation
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
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
b2d3b01 to
3a6b042
Compare
3a6b042 to
5a5c2f7
Compare
d5b7839 to
54c3837
Compare
54c3837 to
1f9585b
Compare
5a5c2f7 to
60d2a44
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}/storesrefreshIfPageError#stores-tbody trto render, then scroll-to-bottom in a loop until row count stabilizes (lazy-loaded table)e2e-w), deduplicate viaSet--listmode (per store):admin.shopify.com/store/{slug}/settings/appsPromise.race— either empty state or first app menu buttonbutton#nextURL)Default mode (per store):
admin.shopify.com/store/{slug}/settings/appsPromise.race— either empty state or first app menu buttondeleteStore()(fromsetup/store.ts) with up to 3 retries--deletemode: same as default but skips stores that have apps installed (step 4 → skip if not empty).Uninstall logic (
uninstallAllAppsFromStore):isStoreAppsEmpty— if true, done (primary termination)button[aria-label="More actions"]at positionconsecutiveSkipsdiv[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 scroll-based lazy loading. 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 --headedcleanup-stores.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