Skip to content

Commit 62847d0

Browse files
committed
refactor to address review feedback
1 parent c399166 commit 62847d0

9 files changed

Lines changed: 79 additions & 95 deletions

File tree

.github/workflows/tests-pr.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,6 @@ jobs:
242242
working-directory: packages/e2e
243243
env:
244244
SHOPIFY_FLAG_CLIENT_ID: ${{ secrets.E2E_CLIENT_ID }}
245-
SHOPIFY_CLI_PARTNERS_TOKEN: ${{ secrets.SHOPIFY_CLI_PARTNERS_TOKEN }}
246245
E2E_ACCOUNT_EMAIL: ${{ secrets.E2E_ACCOUNT_EMAIL }}
247246
E2E_ACCOUNT_PASSWORD: ${{ secrets.E2E_ACCOUNT_PASSWORD }}
248247
E2E_STORE_FQDN: ${{ secrets.E2E_STORE_FQDN }}

packages/e2e/helpers/load-env.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.

packages/e2e/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"devDependencies": {
3232
"@playwright/test": "^1.50.0",
3333
"@types/node": "18.19.70",
34+
"dotenv": "16.4.7",
3435
"execa": "^7.2.0",
3536
"node-pty": "^1.0.0",
3637
"strip-ansi": "^7.1.0"

packages/e2e/playwright.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/* eslint-disable line-comment-position */
2-
import {loadEnv} from './helpers/load-env.js'
2+
import {config} from 'dotenv'
33
import {defineConfig} from '@playwright/test'
44

5-
loadEnv(import.meta.url)
5+
config()
66

77
const isCI = Boolean(process.env.CI)
88

packages/e2e/setup/app.ts

Lines changed: 22 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,17 @@
11
/* eslint-disable no-restricted-imports, no-await-in-loop */
22
import {authFixture} from './auth.js'
3+
import {navigateToDashboard} from './browser.js'
34
import * as path from 'path'
45
import * 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.
2013
const 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. */
215173
export 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(/\/apps\/\d+/)) continue
227185

228-
const name = text.split(/\d+ install/)[0]?.trim() ?? text.split('\n')[0]?.trim() ?? text.trim()
186+
const name = text.split(/\d+\s+install/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

packages/e2e/setup/auth.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,6 @@ import {execa} from 'execa'
1717
export const authFixture = browserFixture.extend<{}, {authLogin: void}>({
1818
authLogin: [
1919
async ({env, browserPage}, use) => {
20-
// Remove the partners token BEFORE any CLI commands run (and before the early-return).
21-
// The token takes highest priority in the CLI auth chain (getAppAutomationToken in session.ts).
22-
// When present, the CLI exchanges it for an App Management token that can't create apps (403).
23-
// We must delete from BOTH env.processEnv AND process.env because execa merges its env
24-
// option with the parent's process.env by default (extendEnv: true).
25-
delete env.processEnv.SHOPIFY_CLI_PARTNERS_TOKEN
26-
delete process.env.SHOPIFY_CLI_PARTNERS_TOKEN
27-
2820
const email = process.env.E2E_ACCOUNT_EMAIL
2921
const password = process.env.E2E_ACCOUNT_PASSWORD
3022

packages/e2e/setup/browser.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
import {cliFixture} from './cli.js'
22
import {chromium, type Page} from '@playwright/test'
33

4+
// ---------------------------------------------------------------------------
5+
// Shared browser context type
6+
// ---------------------------------------------------------------------------
7+
8+
export interface BrowserContext {
9+
browserPage: Page
10+
}
11+
12+
// ---------------------------------------------------------------------------
13+
// Fixture
14+
// ---------------------------------------------------------------------------
15+
416
/**
517
* Worker-scoped fixture providing a persistent Playwright browser page.
618
*
@@ -29,3 +41,37 @@ export const browserFixture = cliFixture.extend<{}, {browserPage: Page}>({
2941
{scope: 'worker'},
3042
],
3143
})
44+
45+
// ---------------------------------------------------------------------------
46+
// Browser helpers — generic dashboard navigation
47+
// ---------------------------------------------------------------------------
48+
/** Navigate to the dev dashboard for the configured org. */
49+
export async function navigateToDashboard(
50+
ctx: BrowserContext & {
51+
email?: string
52+
orgId?: string
53+
},
54+
): Promise<void> {
55+
const {browserPage} = ctx
56+
const orgId = ctx.orgId ?? (process.env.E2E_ORG_ID ?? '').trim()
57+
const dashboardUrl = orgId ? `https://dev.shopify.com/dashboard/${orgId}/apps` : 'https://dev.shopify.com/dashboard'
58+
await browserPage.goto(dashboardUrl, {waitUntil: 'domcontentloaded'})
59+
await browserPage.waitForTimeout(3000)
60+
61+
// Handle account picker (skip if email not provided)
62+
if (ctx.email) {
63+
const accountButton = browserPage.locator(`text=${ctx.email}`).first()
64+
if (await accountButton.isVisible({timeout: 5000}).catch(() => false)) {
65+
await accountButton.click()
66+
await browserPage.waitForTimeout(3000)
67+
}
68+
}
69+
70+
// Retry on 500 errors
71+
for (let attempt = 1; attempt <= 3; attempt++) {
72+
const pageText = (await browserPage.textContent('body')) ?? '' // eslint-disable-line no-await-in-loop
73+
if (!pageText.includes('500') && !pageText.includes('Internal Server Error')) break
74+
await browserPage.waitForTimeout(3000) // eslint-disable-line no-await-in-loop
75+
await browserPage.reload({waitUntil: 'domcontentloaded'}) // eslint-disable-line no-await-in-loop
76+
}
77+
}

packages/e2e/setup/cli.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,4 +239,9 @@ export const cliFixture = envFixture.extend<{cli: CLIProcess}>({
239239
},
240240
})
241241

242+
export interface CLIContext {
243+
cli: CLIProcess
244+
appDir: string
245+
}
246+
242247
export {type E2EEnv}

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)