Skip to content

Commit 603fe61

Browse files
committed
refactor to address review feedback
1 parent c451714 commit 603fe61

9 files changed

Lines changed: 75 additions & 93 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: 18 additions & 52 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
// ---------------------------------------------------------------------------
@@ -176,40 +165,9 @@ export async function configLink(
176165
}
177166

178167
// ---------------------------------------------------------------------------
179-
// Browser helpers — use Playwright for actions without CLI commands
168+
// Browser helpers — app-specific dashboard automation
180169
// ---------------------------------------------------------------------------
181170

182-
/** Navigate to the dev dashboard for the configured org. */
183-
export async function navigateToDashboard(
184-
ctx: BrowserContext & {
185-
email?: string
186-
orgId?: string
187-
},
188-
): Promise<void> {
189-
const {browserPage} = ctx
190-
const orgId = ctx.orgId ?? (process.env.E2E_ORG_ID ?? '').split('#')[0]!.trim()
191-
const dashboardUrl = orgId ? `https://dev.shopify.com/dashboard/${orgId}/apps` : 'https://dev.shopify.com/dashboard'
192-
await browserPage.goto(dashboardUrl, {waitUntil: 'domcontentloaded'})
193-
await browserPage.waitForTimeout(3000)
194-
195-
// Handle account picker (skip if email not provided)
196-
if (ctx.email) {
197-
const accountButton = browserPage.locator(`text=${ctx.email}`).first()
198-
if (await accountButton.isVisible({timeout: 5000}).catch(() => false)) {
199-
await accountButton.click()
200-
await browserPage.waitForTimeout(3000)
201-
}
202-
}
203-
204-
// Retry on 500 errors
205-
for (let attempt = 1; attempt <= 3; attempt++) {
206-
await browserPage.waitForTimeout(3000)
207-
const pageText = (await browserPage.textContent('body')) ?? ''
208-
if (!pageText.includes('500') && !pageText.includes('Internal Server Error')) break
209-
await browserPage.reload({waitUntil: 'domcontentloaded'})
210-
}
211-
}
212-
213171
/** Find apps matching a name pattern on the dashboard. Call navigateToDashboard first. */
214172
export async function findAppsOnDashboard(
215173
ctx: BrowserContext & {
@@ -224,7 +182,7 @@ export async function findAppsOnDashboard(
224182
const text = await card.textContent()
225183
if (!href || !text || !href.match(/\/apps\/\d+/)) continue
226184

227-
const name = text.split(/\d+ install/)[0]?.trim() ?? text.split('\n')[0]?.trim() ?? text.trim()
185+
const name = text.split(/\d+\s+install/i)[0]?.trim() ?? text.split('\n')[0]?.trim() ?? text.trim()
228186
if (!name || name.length > 200) continue
229187
if (!name.includes(ctx.namePattern)) continue
230188

@@ -244,7 +202,7 @@ export async function uninstallApp(
244202
},
245203
): Promise<boolean> {
246204
const {browserPage, appUrl, appName} = ctx
247-
const orgId = ctx.orgId ?? (process.env.E2E_ORG_ID ?? '').split('#')[0]!.trim()
205+
const orgId = ctx.orgId ?? (process.env.E2E_ORG_ID ?? '').split('#')[0]?.trim() ?? ''
248206

249207
await browserPage.goto(`${appUrl}/installs`, {waitUntil: 'domcontentloaded'})
250208
await browserPage.waitForTimeout(3000)
@@ -391,13 +349,21 @@ export async function cleanupApp(
391349
await uninstallApp({browserPage: ctx.browserPage, appUrl: app.url, appName: app.name, orgId: ctx.orgId})
392350
await deleteApp({browserPage: ctx.browserPage, appUrl: app.url})
393351
// eslint-disable-next-line no-catch-all/no-catch-all
394-
} catch (_err) {
352+
} catch (err) {
395353
// Best-effort per app — continue cleaning up remaining apps
354+
if (process.env.DEBUG === '1') {
355+
const msg = err instanceof Error ? err.message : String(err)
356+
process.stderr.write(`[e2e] Cleanup failed for app ${app.name}: ${msg}\n`)
357+
}
396358
}
397359
}
398360
// eslint-disable-next-line no-catch-all/no-catch-all
399-
} catch (_err) {
361+
} catch (err) {
400362
// Best-effort — don't fail the test if cleanup fails
363+
if (process.env.DEBUG === '1') {
364+
const msg = err instanceof Error ? err.message : String(err)
365+
process.stderr.write(`[e2e] Cleanup failed for ${ctx.appName}: ${msg}\n`)
366+
}
401367
}
402368
}
403369

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 ?? '').split('#')[0]?.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+
await browserPage.waitForTimeout(3000) // eslint-disable-line no-await-in-loop
73+
const pageText = (await browserPage.textContent('body')) ?? '' // eslint-disable-line no-await-in-loop
74+
if (!pageText.includes('500') && !pageText.includes('Internal Server Error')) break
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)