Skip to content

Commit 1a46d87

Browse files
Merge pull request #7367 from Shopify/psyw-0421-E2E-QA-A-app-dev
E2E - QA: init, deploy, versions list, config link, 2nd deploy
2 parents 2de9f35 + de35fc6 commit 1a46d87

1 file changed

Lines changed: 120 additions & 16 deletions

File tree

packages/e2e/tests/app-deploy.spec.ts

Lines changed: 120 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,159 @@
1-
import {appTestFixture as test, createApp, deployApp, versionsList} from '../setup/app.js'
1+
import {appTestFixture as test, createApp, deployApp, versionsList, configLink} from '../setup/app.js'
22
import {teardownAll} from '../setup/teardown.js'
33
import {TEST_TIMEOUT} from '../setup/constants.js'
44
import {requireEnv} from '../setup/env.js'
55
import {expect} from '@playwright/test'
66
import * as fs from 'fs'
7-
import * as path from 'path' // eslint-disable-line no-restricted-imports
7+
// eslint-disable-next-line no-restricted-imports
8+
import * as path from 'path'
9+
10+
/**
11+
* Test A — full deploy lifecycle (QA checklist: Apps section, deploy flow).
12+
*
13+
* 1. `app init` Create primary app (React Router + JavaScript)
14+
* 2. `app deploy --version v1` Deploy with a version tag
15+
* 3. `app versions list` Verify the primary tag is active and no other
16+
* version is stuck active
17+
* 4. `app config link` from primary dir → creates a brand-new secondary app
18+
* interactively (answers org → "Create new?" → app name; config name
19+
* prompt is skipped via `--config secondary`)
20+
* 5. `app deploy --config secondary` Deploy from primary dir to secondary app
21+
* 6. `app versions list --config secondary` Verify the secondary deploy hit
22+
* the secondary app (not a silent fallback to primary) and the primary
23+
* tag does not leak into secondary's list
24+
*
25+
* Test body is pure CLI; teardown uses the dev dashboard to delete both apps.
26+
*/
27+
28+
interface VersionLine {
29+
versionTag?: string | null
30+
status: string
31+
}
32+
33+
/**
34+
* Asserts a `versions list --json` result shows:
35+
* - `expectedTag` is present and `active`
36+
* - no other version is stuck `active`
37+
* - (if `forbiddenTag` provided) `forbiddenTag` does not appear at all
38+
*
39+
* The last check guards cross-app leakage: a version we expect to live on one
40+
* app should never appear in another app's version list.
41+
*/
42+
function assertActiveVersion(opts: {
43+
result: {stdout: string; stderr: string; exitCode: number}
44+
expectedTag: string
45+
step: string
46+
forbiddenTag?: string
47+
}) {
48+
const {result, expectedTag, step, forbiddenTag} = opts
49+
const output = result.stdout + result.stderr
50+
expect(result.exitCode, `${step} - versions list failed:\n${output}`).toBe(0)
51+
const versions = JSON.parse(result.stdout) as VersionLine[]
52+
const entry = versions.find((version) => version.versionTag === expectedTag)
53+
expect(entry, `${step} - version tag "${expectedTag}" not found in:\n${result.stdout}`).toBeDefined()
54+
expect(entry?.status, `${step} - expected "${expectedTag}" to be active, got "${entry?.status}"`).toBe('active')
55+
const otherActive = versions.filter((version) => version.versionTag !== expectedTag && version.status === 'active')
56+
expect(otherActive, `${step} - unexpected other active versions: ${JSON.stringify(otherActive)}`).toHaveLength(0)
57+
if (forbiddenTag) {
58+
const tags = versions.map((version) => version.versionTag)
59+
expect(tags, `${step} - tag "${forbiddenTag}" unexpectedly found in list`).not.toContain(forbiddenTag)
60+
}
61+
}
862

963
test.describe('App deploy', () => {
10-
test('deploy and verify version exists', async ({cli, env, browserPage}) => {
64+
test('init, deploy, versions list, config link, deploy to secondary', async ({cli, env, browserPage}) => {
1165
test.setTimeout(TEST_TIMEOUT.long)
1266
requireEnv(env, 'orgId')
1367

1468
const parentDir = fs.mkdtempSync(path.join(env.tempDir, 'app-'))
15-
const appName = `E2E-deploy-${Date.now()}`
69+
const appName = `E2E-deploy1-${Date.now()}`
70+
const secondaryAppName = `E2E-deploy2-${Date.now()}`
1671

1772
try {
18-
// Step 1: Create an extension-only app (no scopes needed for deploy)
73+
// Step 1: Create primary app (React Router template)
1974
const initResult = await createApp({
2075
cli,
2176
parentDir,
2277
name: appName,
23-
template: 'none',
78+
template: 'reactRouter',
79+
flavor: 'javascript',
2480
packageManager: 'pnpm',
2581
orgId: env.orgId,
2682
})
27-
expect(initResult.exitCode, `createApp failed:\nstdout: ${initResult.stdout}\nstderr: ${initResult.stderr}`).toBe(
28-
0,
29-
)
83+
expect(initResult.exitCode, `Step 1 - primary app init failed:\n${initResult.stderr}`).toBe(0)
3084
const appDir = initResult.appDir
3185

3286
// Step 2: Deploy with a tagged version
33-
const versionTag = `e2e-v-${Date.now()}`
34-
const deployResult = await deployApp({cli, appDir, version: versionTag, message: 'E2E test deployment'})
87+
const versionTag = `E2E-v1-${Date.now()}`
88+
const deployResult = await deployApp({cli, appDir, version: versionTag, message: 'E2E A primary deployment'})
3589
const deployOutput = deployResult.stdout + deployResult.stderr
36-
expect(deployResult.exitCode, `deploy failed:\n${deployOutput}`).toBe(0)
90+
expect(deployResult.exitCode, `Step 2 - deploy failed:\n${deployOutput}`).toBe(0)
3791

38-
// Step 3: Verify the version exists via versions list
92+
// Step 3: Verify the primary tag is active and no other version is stuck active.
3993
const listResult = await versionsList({cli, appDir})
40-
const listOutput = listResult.stdout + listResult.stderr
41-
expect(listResult.exitCode, `versions list failed:\n${listOutput}`).toBe(0)
42-
expect(listOutput).toContain(versionTag)
94+
assertActiveVersion({result: listResult, expectedTag: versionTag, step: 'Step 3'})
95+
96+
// Step 4: Config link from primary dir → creates a brand-new secondary app
97+
// interactively (org → "Create new?" → "App name"). The "Configuration file
98+
// name" prompt is skipped via `--config secondary`.
99+
const secondaryConfig = 'secondary'
100+
const linkResult = await configLink({
101+
cli,
102+
appDir,
103+
appName: secondaryAppName,
104+
orgId: env.orgId,
105+
configName: secondaryConfig,
106+
})
107+
const linkOutput = linkResult.stdout + linkResult.stderr
108+
expect(linkResult.exitCode, `Step 4 - config link failed:\n${linkOutput}`).toBe(0)
109+
expect(linkOutput, `Step 4 - missing "is now linked to \\"${secondaryAppName}\\""`).toContain(
110+
`is now linked to "${secondaryAppName}"`,
111+
)
112+
const secondaryTomlPath = path.join(appDir, `shopify.app.${secondaryConfig}.toml`)
113+
expect(
114+
fs.existsSync(secondaryTomlPath),
115+
`Step 4 - expected ${secondaryTomlPath} to exist after config link`,
116+
).toBe(true)
117+
118+
// Step 5: Deploy from primary dir to secondary app via --config secondary
119+
const secondaryVersionTag = `E2E-v2-${Date.now()}`
120+
const secondaryDeployResult = await deployApp({
121+
cli,
122+
appDir,
123+
config: secondaryConfig,
124+
version: secondaryVersionTag,
125+
message: 'E2E A secondary deployment',
126+
})
127+
const secondaryDeployOutput = secondaryDeployResult.stdout + secondaryDeployResult.stderr
128+
expect(secondaryDeployResult.exitCode, `Step 5 - secondary deploy failed:\n${secondaryDeployOutput}`).toBe(0)
129+
130+
// Step 6: Verify the secondary deploy hit the secondary app (not a silent
131+
// fallback to primary). Checks the secondary tag is active, no other
132+
// version is stuck active, and the primary tag doesn't leak into secondary.
133+
const secondaryListResult = await versionsList({cli, appDir, config: secondaryConfig})
134+
assertActiveVersion({
135+
result: secondaryListResult,
136+
expectedTag: secondaryVersionTag,
137+
step: 'Step 6',
138+
forbiddenTag: versionTag,
139+
})
43140
} finally {
44141
// E2E_SKIP_TEARDOWN=1 skips teardown for debugging. Run cleanup scripts afterward.
45142
if (!process.env.E2E_SKIP_TEARDOWN) {
46143
fs.rmSync(parentDir, {recursive: true, force: true})
144+
// Neither app was installed on a store — delete the apps only (no uninstall)
47145
await teardownAll({
48146
browserPage,
49147
appName,
50148
orgId: env.orgId,
51149
workerIndex: env.workerIndex,
52150
})
151+
await teardownAll({
152+
browserPage,
153+
appName: secondaryAppName,
154+
orgId: env.orgId,
155+
workerIndex: env.workerIndex,
156+
})
53157
}
54158
}
55159
})

0 commit comments

Comments
 (0)