Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions packages/e2e/setup/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ export async function createApp(ctx: {
// ---------------------------------------------------------------------------

/**
* Read the client_id from a shopify.app.toml file.
* Read the client_id from a Shopify app TOML file.
*/
export function extractClientId(appDir: string): string {
const tomlPath = path.join(appDir, 'shopify.app.toml')
export function extractClientId(appDir: string, configName?: string): string {
const tomlPath = path.join(appDir, configName ? `shopify.app.${configName}.toml` : 'shopify.app.toml')
const parsed = toml.parse(fs.readFileSync(tomlPath, 'utf8'))
const clientId = parsed.client_id as string | undefined
if (!clientId) {
Expand All @@ -110,6 +110,13 @@ export function extractClientId(appDir: string): string {
return clientId
}

/**
* Build the direct Dev Dashboard URL for an app from its local TOML client_id.
*/
export function devDashboardAppUrl(appDir: string, orgId: string, configName?: string): string {
return `https://dev.shopify.com/dashboard/${orgId}/apps/${extractClientId(appDir, configName)}`
}

/**
* Merge an E2E TOML fixture into the app config generated by the template.
* Template-owned fields not mentioned by the fixture are preserved.
Expand Down
1 change: 1 addition & 0 deletions packages/e2e/setup/teardown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export async function teardownAll(ctx: TeardownCtx): Promise<void> {
for (let attempt = 1; attempt <= 3; attempt++) {
try {
const appUrl = ctx.appUrl ?? (await findAppOnDevDashboard(page, ctx.appName, ctx.orgId))
log.log(wCtx, ctx.appUrl ? 'using direct app URL for delete' : 'using dashboard search for delete')
if (!appUrl) {
// null could mean "app not in the list" OR "pagination ended on a stuck error page"
// — findAppOnDevDashboard's refresh-on-error doesn't cover every iteration.
Expand Down
15 changes: 12 additions & 3 deletions packages/e2e/tests/app-deploy.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import {appTestFixture as test, createApp, deployApp, versionsList, configLink} from '../setup/app.js'
import {
appTestFixture as test,
createApp,
deployApp,
devDashboardAppUrl,
versionsList,
configLink,
} from '../setup/app.js'
import {teardownAll} from '../setup/teardown.js'
import {TEST_TIMEOUT} from '../setup/constants.js'
import {e2eAppName, requireEnv} from '../setup/env.js'
Expand Down Expand Up @@ -98,6 +105,7 @@ test.describe('App deploy', () => {
})
expect(initResult.exitCode, `Step 1 - primary app init failed:\n${initResult.stderr}`).toBe(0)
const appDir = initResult.appDir
primaryAppUrl = devDashboardAppUrl(appDir, env.orgId)

// Step 2: Deploy with a tagged version
const versionTag = `E2E-v1-${Date.now()}`
Expand All @@ -120,7 +128,7 @@ test.describe('App deploy', () => {
)
const deployOutput = deployResult.stdout + deployResult.stderr
expect(deployResult.exitCode, `Step 2 - deploy failed:\n${deployOutput}`).toBe(0)
primaryAppUrl = devDashboardAppUrlFromOutput(deployOutput)
primaryAppUrl = devDashboardAppUrlFromOutput(deployOutput) ?? primaryAppUrl
assertDeployAnalytics({output: deployOutput, appName, step: 'Step 2'})

// Step 3: Verify the primary tag is active and no other version is stuck active.
Expand Down Expand Up @@ -148,6 +156,7 @@ test.describe('App deploy', () => {
fs.existsSync(secondaryTomlPath),
`Step 4 - expected ${secondaryTomlPath} to exist after config link`,
).toBe(true)
secondaryAppUrl = devDashboardAppUrl(appDir, env.orgId, secondaryConfig)

// Step 5: Deploy from primary dir to secondary app via --config secondary
const secondaryVersionTag = `E2E-v2-${Date.now()}`
Expand All @@ -160,7 +169,7 @@ test.describe('App deploy', () => {
})
const secondaryDeployOutput = secondaryDeployResult.stdout + secondaryDeployResult.stderr
expect(secondaryDeployResult.exitCode, `Step 5 - secondary deploy failed:\n${secondaryDeployOutput}`).toBe(0)
secondaryAppUrl = devDashboardAppUrlFromOutput(secondaryDeployOutput)
secondaryAppUrl = devDashboardAppUrlFromOutput(secondaryDeployOutput) ?? secondaryAppUrl

// Step 6: Verify the secondary deploy hit the secondary app (not a silent
// fallback to primary). Checks the secondary tag is active, no other
Expand Down
5 changes: 4 additions & 1 deletion packages/e2e/tests/app-dev-server.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {createApp} from '../setup/app.js'
import {createApp, devDashboardAppUrl} from '../setup/app.js'
import {teardownAll} from '../setup/teardown.js'
import {CLI_TIMEOUT, TEST_TIMEOUT} from '../setup/constants.js'
import {e2eAppName, requireEnv} from '../setup/env.js'
Expand All @@ -14,6 +14,7 @@ test.describe('App dev server', () => {

const parentDir = fs.mkdtempSync(path.join(env.tempDir, 'app-'))
const appName = e2eAppName('dev')
let appUrl: string | undefined

try {
// Step 1: Create an extension-only app (no scopes needed)
Expand All @@ -29,6 +30,7 @@ test.describe('App dev server', () => {
0,
)
const appDir = initResult.appDir
appUrl = devDashboardAppUrl(appDir, env.orgId)

// Step 2: Start dev server via PTY, targeting the worker's store
const dev = await cli.spawn(['app', 'dev', '--path', appDir], {
Expand All @@ -55,6 +57,7 @@ test.describe('App dev server', () => {
await teardownAll({
browserPage,
appName,
appUrl,
orgId: env.orgId,
storeFqdn,
workerIndex: env.workerIndex,
Expand Down
11 changes: 10 additions & 1 deletion packages/e2e/tests/app-scaffold.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable no-restricted-imports */
import {appTestFixture as test, createApp, buildApp, generateExtension} from '../setup/app.js'
import {appTestFixture as test, createApp, buildApp, devDashboardAppUrl, generateExtension} from '../setup/app.js'
import {teardownAll} from '../setup/teardown.js'
import {TEST_TIMEOUT} from '../setup/constants.js'
import {e2eAppName, requireEnv} from '../setup/env.js'
Expand All @@ -14,6 +14,7 @@ test.describe('App scaffold', () => {

const parentDir = fs.mkdtempSync(path.join(env.tempDir, 'app-'))
const appName = e2eAppName('scaffold')
let appUrl: string | undefined

try {
// Step 1: Create a new app from the react-router template
Expand All @@ -32,6 +33,7 @@ test.describe('App scaffold', () => {
const initOutput = initResult.stdout + initResult.stderr
expect(initOutput).toContain('is ready for you to build!')
const appDir = initResult.appDir
appUrl = devDashboardAppUrl(appDir, env.orgId)

// Step 2: Verify the app directory was created with expected files
expect(fs.existsSync(appDir)).toBe(true)
Expand All @@ -51,6 +53,7 @@ test.describe('App scaffold', () => {
await teardownAll({
browserPage,
appName,
appUrl,
orgId: env.orgId,
workerIndex: env.workerIndex,
})
Expand All @@ -64,6 +67,7 @@ test.describe('App scaffold', () => {

const parentDir = fs.mkdtempSync(path.join(env.tempDir, 'app-'))
const appName = e2eAppName('ext-only')
let appUrl: string | undefined

try {
const initResult = await createApp({
Expand All @@ -77,6 +81,7 @@ test.describe('App scaffold', () => {
expect(initResult.exitCode, `createApp failed:\nstdout: ${initResult.stdout}\nstderr: ${initResult.stderr}`).toBe(
0,
)
appUrl = devDashboardAppUrl(initResult.appDir, env.orgId)
expect(fs.existsSync(initResult.appDir)).toBe(true)
expect(fs.existsSync(path.join(initResult.appDir, 'shopify.app.toml'))).toBe(true)
} finally {
Expand All @@ -86,6 +91,7 @@ test.describe('App scaffold', () => {
await teardownAll({
browserPage,
appName,
appUrl,
orgId: env.orgId,
workerIndex: env.workerIndex,
})
Expand All @@ -102,6 +108,7 @@ test.describe('App scaffold', () => {

const parentDir = fs.mkdtempSync(path.join(env.tempDir, 'app-'))
const appName = e2eAppName('ext-gen')
let appUrl: string | undefined

try {
const initResult = await createApp({
Expand All @@ -117,6 +124,7 @@ test.describe('App scaffold', () => {
0,
)
const appDir = initResult.appDir
appUrl = devDashboardAppUrl(appDir, env.orgId)

const extensionConfigs = [
{name: 'test-product-sub', template: 'product_subscription_ui', flavor: 'react'},
Expand Down Expand Up @@ -144,6 +152,7 @@ test.describe('App scaffold', () => {
await teardownAll({
browserPage,
appName,
appUrl,
orgId: env.orgId,
workerIndex: env.workerIndex,
})
Expand Down
11 changes: 10 additions & 1 deletion packages/e2e/tests/dev-hot-reload.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-console */
/* eslint-disable no-restricted-imports */
import {createApp, injectFixtureToml} from '../setup/app.js'
import {createApp, devDashboardAppUrl, injectFixtureToml} from '../setup/app.js'
import {teardownAll} from '../setup/teardown.js'
import {CLI_TIMEOUT, TEST_TIMEOUT} from '../setup/constants.js'
import {e2eAppName, requireEnv} from '../setup/env.js'
Expand Down Expand Up @@ -45,11 +45,13 @@ test.describe('Dev hot reload', () => {

const parentDir = fs.mkdtempSync(path.join(env.tempDir, 'app-'))
const appName = e2eAppName('hot-reload')
let appUrl: string | undefined

try {
const initResult = await createApp({cli, parentDir, name: appName, template: 'none', orgId: env.orgId})
expect(initResult.exitCode, `createApp failed:\nstderr: ${initResult.stderr}`).toBe(0)
const appDir = initResult.appDir
appUrl = devDashboardAppUrl(appDir, env.orgId)

injectFixtureToml(appDir, FIXTURE_TOML, appName)

Expand Down Expand Up @@ -89,6 +91,7 @@ test.describe('Dev hot reload', () => {
await teardownAll({
browserPage,
appName,
appUrl,
orgId: env.orgId,
storeFqdn,
workerIndex: env.workerIndex,
Expand All @@ -103,11 +106,13 @@ test.describe('Dev hot reload', () => {

const parentDir = fs.mkdtempSync(path.join(env.tempDir, 'app-'))
const appName = e2eAppName('hot-create')
let appUrl: string | undefined

try {
const initResult = await createApp({cli, parentDir, name: appName, template: 'none', orgId: env.orgId})
expect(initResult.exitCode, `createApp failed:\nstderr: ${initResult.stderr}`).toBe(0)
const appDir = initResult.appDir
appUrl = devDashboardAppUrl(appDir, env.orgId)

injectFixtureToml(appDir, FIXTURE_TOML, appName)

Expand Down Expand Up @@ -141,6 +146,7 @@ test.describe('Dev hot reload', () => {
await teardownAll({
browserPage,
appName,
appUrl,
orgId: env.orgId,
storeFqdn,
workerIndex: env.workerIndex,
Expand All @@ -155,11 +161,13 @@ test.describe('Dev hot reload', () => {

const parentDir = fs.mkdtempSync(path.join(env.tempDir, 'app-'))
const appName = e2eAppName('hot-delete')
let appUrl: string | undefined

try {
const initResult = await createApp({cli, parentDir, name: appName, template: 'none', orgId: env.orgId})
expect(initResult.exitCode, `createApp failed:\nstderr: ${initResult.stderr}`).toBe(0)
const appDir = initResult.appDir
appUrl = devDashboardAppUrl(appDir, env.orgId)

injectFixtureToml(appDir, FIXTURE_TOML, appName)

Expand Down Expand Up @@ -199,6 +207,7 @@ test.describe('Dev hot reload', () => {
await teardownAll({
browserPage,
appName,
appUrl,
orgId: env.orgId,
storeFqdn,
workerIndex: env.workerIndex,
Expand Down
8 changes: 7 additions & 1 deletion packages/e2e/tests/multi-config-dev.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-console */
/* eslint-disable no-restricted-imports */
import {createApp, extractClientId, injectFixtureToml} from '../setup/app.js'
import {createApp, devDashboardAppUrl, extractClientId, injectFixtureToml} from '../setup/app.js'
import {teardownAll} from '../setup/teardown.js'
import {CLI_TIMEOUT, TEST_TIMEOUT} from '../setup/constants.js'
import {e2eAppName, requireEnv} from '../setup/env.js'
Expand All @@ -20,11 +20,13 @@ test.describe('Multi-config dev', () => {

const parentDir = fs.mkdtempSync(path.join(env.tempDir, 'app-'))
const appName = e2eAppName('multi-cfg')
let appUrl: string | undefined

try {
const initResult = await createApp({cli, parentDir, name: appName, template: 'none', orgId: env.orgId})
expect(initResult.exitCode, `createApp failed:\nstderr: ${initResult.stderr}`).toBe(0)
const appDir = initResult.appDir
appUrl = devDashboardAppUrl(appDir, env.orgId)

// Inject the fully populated TOML as the default config
injectFixtureToml(appDir, FIXTURE_TOML, appName)
Expand Down Expand Up @@ -93,6 +95,7 @@ extensions_summary = "E2E staging app extensions"
await teardownAll({
browserPage,
appName,
appUrl,
orgId: env.orgId,
storeFqdn,
workerIndex: env.workerIndex,
Expand All @@ -107,11 +110,13 @@ extensions_summary = "E2E staging app extensions"

const parentDir = fs.mkdtempSync(path.join(env.tempDir, 'app-'))
const appName = e2eAppName('mcfg-def')
let appUrl: string | undefined

try {
const initResult = await createApp({cli, parentDir, name: appName, template: 'none', orgId: env.orgId})
expect(initResult.exitCode, `createApp failed:\nstderr: ${initResult.stderr}`).toBe(0)
const appDir = initResult.appDir
appUrl = devDashboardAppUrl(appDir, env.orgId)

injectFixtureToml(appDir, FIXTURE_TOML, appName)
const clientId = extractClientId(appDir)
Expand Down Expand Up @@ -168,6 +173,7 @@ extensions_summary = "E2E staging app extensions"
await teardownAll({
browserPage,
appName,
appUrl,
orgId: env.orgId,
storeFqdn,
workerIndex: env.workerIndex,
Expand Down
8 changes: 7 additions & 1 deletion packages/e2e/tests/toml-config.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-console */
/* eslint-disable no-restricted-imports */
import {createApp, injectFixtureToml} from '../setup/app.js'
import {createApp, devDashboardAppUrl, injectFixtureToml} from '../setup/app.js'
import {teardownAll} from '../setup/teardown.js'
import {CLI_TIMEOUT, TEST_TIMEOUT} from '../setup/constants.js'
import {e2eAppName, requireEnv} from '../setup/env.js'
Expand All @@ -20,6 +20,7 @@ test.describe('TOML config regression', () => {

const parentDir = fs.mkdtempSync(path.join(env.tempDir, 'app-'))
const appName = e2eAppName('toml-deploy')
let appUrl: string | undefined

try {
const initResult = await createApp({cli, parentDir, name: appName, template: 'none', orgId: env.orgId})
Expand All @@ -28,6 +29,7 @@ test.describe('TOML config regression', () => {

// Overwrite with fully populated TOML fixture (injects the real client_id)
injectFixtureToml(appDir, FIXTURE_TOML, appName)
appUrl = devDashboardAppUrl(appDir, env.orgId)

const result = await cli.exec(['app', 'deploy', '--path', appDir, '--allow-updates', '--allow-deletes'], {
timeout: CLI_TIMEOUT.long,
Expand All @@ -41,6 +43,7 @@ test.describe('TOML config regression', () => {
await teardownAll({
browserPage,
appName,
appUrl,
orgId: env.orgId,
workerIndex: env.workerIndex,
})
Expand All @@ -54,13 +57,15 @@ test.describe('TOML config regression', () => {

const parentDir = fs.mkdtempSync(path.join(env.tempDir, 'app-'))
const appName = e2eAppName('toml-dev')
let appUrl: string | undefined

try {
const initResult = await createApp({cli, parentDir, name: appName, template: 'none', orgId: env.orgId})
expect(initResult.exitCode, `createApp failed:\nstderr: ${initResult.stderr}`).toBe(0)
const appDir = initResult.appDir

injectFixtureToml(appDir, FIXTURE_TOML, appName)
appUrl = devDashboardAppUrl(appDir, env.orgId)

const proc = await cli.spawn(['app', 'dev', '--path', appDir], {env: {CI: '', SHOPIFY_FLAG_STORE: storeFqdn}})

Expand All @@ -83,6 +88,7 @@ test.describe('TOML config regression', () => {
await teardownAll({
browserPage,
appName,
appUrl,
orgId: env.orgId,
storeFqdn,
workerIndex: env.workerIndex,
Expand Down
Loading