Problem/Feature
The E2E test infrastructure (packages/e2e/) has grown organically across multiple PRs. Several problems have emerged:
Duplicated browser automation logic:
- Login + account picker flow is repeated in
setup/auth.ts, setup/global-auth.ts, scripts/cleanup-apps.ts, scripts/cleanup-stores.ts
- Dev Console dismissal is duplicated across
setup/store.ts, setup/app.ts, scripts/cleanup-stores.ts
- Browser creation (loadtest header, WebAuthn disable, storageState loading, default timeouts) is duplicated in
setup/browser.ts, setup/global-auth.ts, scripts/cleanup-apps.ts, scripts/cleanup-stores.ts
- PTY spawn + env filtering is identical in
setup/cli.ts, setup/auth.ts, setup/global-auth.ts
- App finding/deleting logic exists in both
setup/app.ts and scripts/cleanup-apps.ts
- Store uninstall/delete logic exists in both
setup/store.ts and scripts/cleanup-stores.ts
No clear folder boundaries:
setup/ mixes Playwright fixtures (appTestFixture, storeTestFixture), browser page actions (uninstallAppFromStoreAdmin, deleteStoreFromAdmin), CLI helpers (createApp, deployApp), and lifecycle hooks (global-auth.ts)
helpers/ has only two files (browser-login.ts, strip-ansi.ts) that could live elsewhere
- No separation between "how to interact with a page" and "how to wire up test lifecycle"
This makes it hard to:
- Know where to add new browser automation (do I put it in
setup/app.ts? scripts/cleanup.ts? A new file?)
- Avoid duplication when a new script or test needs the same page interaction
- Understand dependencies between files
Proposed Solution
Adopt the Page Object pattern (industry standard for Playwright/Selenium, used by admin-web) to establish clear dependency boundaries.
New folder structure:
packages/e2e/
├── pages/ — Browser page objects (one per domain/page)
│ ├── accounts.ts — accounts.shopify.com: login, passkey bypass, account picker
│ ├── dev-dashboard-apps.ts — dev.shopify.com/dashboard/.../apps: find apps, delete app, pagination
│ ├── dev-dashboard-stores.ts — dev.shopify.com/dashboard/.../stores: find stores
│ ├── admin-store.ts — admin.shopify.com/store/.../settings: uninstall app, dismiss Dev Console, delete store
│ └── admin-store-create.ts — admin.shopify.com/store-create/...: create store form (shadow DOM)
│
├── fixtures/ — Playwright lifecycle hooks (setup/teardown only)
│ ├── env.ts — E2EEnv interface, envFixture, createIsolatedEnv
│ ├── cli.ts — cliFixture, CLIProcess, SpawnedProcess
│ ├── browser.ts — browserFixture + browser factory (single place for headers, timeouts, WebAuthn, storageState)
│ ├── auth.ts — authFixture (copies global session or runs login)
│ ├── global-auth.ts — Playwright globalSetup (one-time auth + admin session)
│ ├── app.ts — appTestFixture + CLI app helpers (createApp, deployApp, etc.)
│ ├── store.ts — storeTestFixture + store name generation
│ └── teardown.ts — teardownAll orchestrator
│
├── utils/ — Pure utilities, no Playwright/fixture dependency
│ ├── constants.ts — TEST_TIMEOUT, CLI_TIMEOUT, BROWSER_TIMEOUT
│ ├── extension-templates.ts — REQUIRED_EXTENSIONS, EXTRA_EXTENSIONS
│ ├── logger.ts — createLogger(), e2eSection(), globalLog()
│ ├── strip-ansi.ts — stripAnsi()
│ └── wait-for-text.ts — waitForText()
│
├── scripts/ — Standalone CLI tools (not run by Playwright)
│ ├── cleanup-apps.ts — Bulk app cleanup (uses pages/ for browser actions)
│ └── cleanup-stores.ts — Bulk store cleanup (uses pages/ for browser actions)
│
├── tests/ — Test spec files
└── data/ — Static test fixture files (TOMLs, etc.)
Dependency flow:
tests → fixtures → pages → utils
scripts → pages → utils
No upward imports. pages/ never imports from fixtures/. utils/ never imports from anything except npm packages.
What each page object contains:
| Page object |
Extracted from |
Key functions |
pages/accounts.ts |
helpers/browser-login.ts, account picker logic in browser.ts/store.ts |
fillSensitive(), completeLogin(), handleAccountPicker() |
pages/dev-dashboard-apps.ts |
setup/app.ts, scripts/cleanup-apps.ts |
navigateToDashboard(), findApps(), deleteApp() |
pages/dev-dashboard-stores.ts |
scripts/cleanup-stores.ts |
findStores() |
pages/admin-store.ts |
setup/store.ts, scripts/cleanup-stores.ts |
dismissDevConsole(), uninstallApp(), uninstallAllApps(), countInstalledApps(), deleteStore() |
pages/admin-store-create.ts |
setup/store.ts |
createStore() |
Additional cleanup included in this refactor:
- Browser factory: All browser creation goes through one function in
fixtures/browser.ts that handles the loadtest header, default timeouts, WebAuthn disable, and storageState loading (currently duplicated in 4 places)
- PTY spawn consolidation: Identical
spawnEnv construction in fixtures/cli.ts, fixtures/auth.ts, fixtures/global-auth.ts extracted to a shared helper
- Login consolidation:
completeLogin + account picker handling unified in pages/accounts.ts (currently repeated in 4 files)
- Dev Console dismissal: Shared
dismissDevConsole(page) in pages/admin-store.ts replaces 3 duplicate implementations
- Delete legacy scripts: Remove
scripts/create-test-apps.ts and scripts/cleanup-test-apps.ts (superseded by current cleanup scripts)
- Rename
setup/ → fixtures/: Aligns naming with Playwright conventions
Problem/Feature
The E2E test infrastructure (
packages/e2e/) has grown organically across multiple PRs. Several problems have emerged:Duplicated browser automation logic:
setup/auth.ts,setup/global-auth.ts,scripts/cleanup-apps.ts,scripts/cleanup-stores.tssetup/store.ts,setup/app.ts,scripts/cleanup-stores.tssetup/browser.ts,setup/global-auth.ts,scripts/cleanup-apps.ts,scripts/cleanup-stores.tssetup/cli.ts,setup/auth.ts,setup/global-auth.tssetup/app.tsandscripts/cleanup-apps.tssetup/store.tsandscripts/cleanup-stores.tsNo clear folder boundaries:
setup/mixes Playwright fixtures (appTestFixture,storeTestFixture), browser page actions (uninstallAppFromStoreAdmin,deleteStoreFromAdmin), CLI helpers (createApp,deployApp), and lifecycle hooks (global-auth.ts)helpers/has only two files (browser-login.ts,strip-ansi.ts) that could live elsewhereThis makes it hard to:
setup/app.ts?scripts/cleanup.ts? A new file?)Proposed Solution
Adopt the Page Object pattern (industry standard for Playwright/Selenium, used by admin-web) to establish clear dependency boundaries.
New folder structure:
Dependency flow:
No upward imports.
pages/never imports fromfixtures/.utils/never imports from anything except npm packages.What each page object contains:
pages/accounts.tshelpers/browser-login.ts, account picker logic inbrowser.ts/store.tsfillSensitive(),completeLogin(),handleAccountPicker()pages/dev-dashboard-apps.tssetup/app.ts,scripts/cleanup-apps.tsnavigateToDashboard(),findApps(),deleteApp()pages/dev-dashboard-stores.tsscripts/cleanup-stores.tsfindStores()pages/admin-store.tssetup/store.ts,scripts/cleanup-stores.tsdismissDevConsole(),uninstallApp(),uninstallAllApps(),countInstalledApps(),deleteStore()pages/admin-store-create.tssetup/store.tscreateStore()Additional cleanup included in this refactor:
fixtures/browser.tsthat handles the loadtest header, default timeouts, WebAuthn disable, and storageState loading (currently duplicated in 4 places)spawnEnvconstruction infixtures/cli.ts,fixtures/auth.ts,fixtures/global-auth.tsextracted to a shared helpercompleteLogin+ account picker handling unified inpages/accounts.ts(currently repeated in 4 files)dismissDevConsole(page)inpages/admin-store.tsreplaces 3 duplicate implementationsscripts/create-test-apps.tsandscripts/cleanup-test-apps.ts(superseded by current cleanup scripts)setup/→fixtures/: Aligns naming with Playwright conventions