From 44d2caedb67fef11ba904bd9ecdcb9359fe0adcb Mon Sep 17 00:00:00 2001 From: Alfonso Noriega Date: Thu, 25 Jun 2026 16:45:23 +0200 Subject: [PATCH] Use direct app URLs during E2E teardown Assisted-By: devx/3c9d2be5-81a1-4ad5-90ba-4155c6ff125d --- packages/e2e/setup/app.ts | 13 ++++++++++--- packages/e2e/setup/teardown.ts | 1 + packages/e2e/tests/app-deploy.spec.ts | 15 ++++++++++++--- packages/e2e/tests/app-dev-server.spec.ts | 5 ++++- packages/e2e/tests/app-scaffold.spec.ts | 11 ++++++++++- packages/e2e/tests/dev-hot-reload.spec.ts | 11 ++++++++++- packages/e2e/tests/multi-config-dev.spec.ts | 8 +++++++- packages/e2e/tests/toml-config.spec.ts | 8 +++++++- 8 files changed, 61 insertions(+), 11 deletions(-) diff --git a/packages/e2e/setup/app.ts b/packages/e2e/setup/app.ts index 124daad9aa8..dbdd212cae3 100644 --- a/packages/e2e/setup/app.ts +++ b/packages/e2e/setup/app.ts @@ -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) { @@ -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. diff --git a/packages/e2e/setup/teardown.ts b/packages/e2e/setup/teardown.ts index a4918401127..595f84abf7a 100644 --- a/packages/e2e/setup/teardown.ts +++ b/packages/e2e/setup/teardown.ts @@ -127,6 +127,7 @@ export async function teardownAll(ctx: TeardownCtx): Promise { 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. diff --git a/packages/e2e/tests/app-deploy.spec.ts b/packages/e2e/tests/app-deploy.spec.ts index 7243493edd5..a8c1c0fac37 100644 --- a/packages/e2e/tests/app-deploy.spec.ts +++ b/packages/e2e/tests/app-deploy.spec.ts @@ -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' @@ -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()}` @@ -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. @@ -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()}` @@ -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 diff --git a/packages/e2e/tests/app-dev-server.spec.ts b/packages/e2e/tests/app-dev-server.spec.ts index 41f1991b038..a8d1d880050 100644 --- a/packages/e2e/tests/app-dev-server.spec.ts +++ b/packages/e2e/tests/app-dev-server.spec.ts @@ -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' @@ -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) @@ -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], { @@ -55,6 +57,7 @@ test.describe('App dev server', () => { await teardownAll({ browserPage, appName, + appUrl, orgId: env.orgId, storeFqdn, workerIndex: env.workerIndex, diff --git a/packages/e2e/tests/app-scaffold.spec.ts b/packages/e2e/tests/app-scaffold.spec.ts index 31270c39a4b..7b43090f3c8 100644 --- a/packages/e2e/tests/app-scaffold.spec.ts +++ b/packages/e2e/tests/app-scaffold.spec.ts @@ -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' @@ -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 @@ -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) @@ -51,6 +53,7 @@ test.describe('App scaffold', () => { await teardownAll({ browserPage, appName, + appUrl, orgId: env.orgId, workerIndex: env.workerIndex, }) @@ -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({ @@ -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 { @@ -86,6 +91,7 @@ test.describe('App scaffold', () => { await teardownAll({ browserPage, appName, + appUrl, orgId: env.orgId, workerIndex: env.workerIndex, }) @@ -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({ @@ -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'}, @@ -144,6 +152,7 @@ test.describe('App scaffold', () => { await teardownAll({ browserPage, appName, + appUrl, orgId: env.orgId, workerIndex: env.workerIndex, }) diff --git a/packages/e2e/tests/dev-hot-reload.spec.ts b/packages/e2e/tests/dev-hot-reload.spec.ts index c67f354cd7b..fc115685c79 100644 --- a/packages/e2e/tests/dev-hot-reload.spec.ts +++ b/packages/e2e/tests/dev-hot-reload.spec.ts @@ -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' @@ -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) @@ -89,6 +91,7 @@ test.describe('Dev hot reload', () => { await teardownAll({ browserPage, appName, + appUrl, orgId: env.orgId, storeFqdn, workerIndex: env.workerIndex, @@ -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) @@ -141,6 +146,7 @@ test.describe('Dev hot reload', () => { await teardownAll({ browserPage, appName, + appUrl, orgId: env.orgId, storeFqdn, workerIndex: env.workerIndex, @@ -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) @@ -199,6 +207,7 @@ test.describe('Dev hot reload', () => { await teardownAll({ browserPage, appName, + appUrl, orgId: env.orgId, storeFqdn, workerIndex: env.workerIndex, diff --git a/packages/e2e/tests/multi-config-dev.spec.ts b/packages/e2e/tests/multi-config-dev.spec.ts index 5c2316cf443..69c9c438e78 100644 --- a/packages/e2e/tests/multi-config-dev.spec.ts +++ b/packages/e2e/tests/multi-config-dev.spec.ts @@ -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' @@ -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) @@ -93,6 +95,7 @@ extensions_summary = "E2E staging app extensions" await teardownAll({ browserPage, appName, + appUrl, orgId: env.orgId, storeFqdn, workerIndex: env.workerIndex, @@ -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) @@ -168,6 +173,7 @@ extensions_summary = "E2E staging app extensions" await teardownAll({ browserPage, appName, + appUrl, orgId: env.orgId, storeFqdn, workerIndex: env.workerIndex, diff --git a/packages/e2e/tests/toml-config.spec.ts b/packages/e2e/tests/toml-config.spec.ts index 79189b99822..2a731455253 100644 --- a/packages/e2e/tests/toml-config.spec.ts +++ b/packages/e2e/tests/toml-config.spec.ts @@ -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' @@ -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}) @@ -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, @@ -41,6 +43,7 @@ test.describe('TOML config regression', () => { await teardownAll({ browserPage, appName, + appUrl, orgId: env.orgId, workerIndex: env.workerIndex, }) @@ -54,6 +57,7 @@ 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}) @@ -61,6 +65,7 @@ test.describe('TOML config regression', () => { 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}}) @@ -83,6 +88,7 @@ test.describe('TOML config regression', () => { await teardownAll({ browserPage, appName, + appUrl, orgId: env.orgId, storeFqdn, workerIndex: env.workerIndex,