Skip to content

Commit a0e1685

Browse files
authored
Merge pull request #6940 from Shopify/03-05-rcb_e2e-toml-testing
Add TOML config regression tests
2 parents 3ad93ef + 9097f3f commit a0e1685

8 files changed

Lines changed: 195 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Invalid TOML: malformed syntax (missing closing quote)
2+
client_id = "__E2E_CLIENT_ID__"
3+
name = "Bad Syntax App
4+
application_url = "https://example.com"
5+
embedded = true
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Invalid TOML: bad webhook config (missing required uri)
2+
client_id = "__E2E_CLIENT_ID__"
3+
name = "Invalid Webhook App"
4+
application_url = "https://example.com"
5+
embedded = true
6+
7+
[webhooks]
8+
api_version = "2025-01"
9+
10+
[[webhooks.subscriptions]]
11+
topics = ["products/create"]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Invalid TOML: wrong types for known fields
2+
client_id = "__E2E_CLIENT_ID__"
3+
name = "Wrong Type App"
4+
application_url = "https://example.com"
5+
embedded = "not-a-boolean"
6+
7+
[access_scopes]
8+
scopes = 12345
9+
use_legacy_install_flow = "nope"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "e2e-toml-regression-test",
3+
"version": "1.0.0",
4+
"private": true
5+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Comprehensive shopify.app.toml for E2E regression testing
2+
# client_id is injected at runtime by the toml-app fixture
3+
client_id = "__E2E_CLIENT_ID__"
4+
name = "E2E TOML Regression Test"
5+
application_url = "https://example.com"
6+
embedded = true
7+
8+
[access_scopes]
9+
scopes = "read_products,write_products,read_orders"
10+
use_legacy_install_flow = false
11+
12+
[access.admin]
13+
direct_api_mode = "online"
14+
embedded_app_direct_api_access = true
15+
16+
[auth]
17+
redirect_urls = [
18+
"https://example.com/auth/callback",
19+
"https://example.com/auth/shopify/callback",
20+
]
21+
22+
[webhooks]
23+
api_version = "2025-01"
24+
25+
[[webhooks.subscriptions]]
26+
topics = ["products/create"]
27+
uri = "https://example.com/webhooks/products/create"
28+
29+
[[webhooks.subscriptions]]
30+
topics = ["orders/create"]
31+
uri = "https://example.com/webhooks/orders/create"
32+
33+
[app_proxy]
34+
url = "https://example.com/proxy"
35+
subpath = "e2e-test"
36+
prefix = "apps"
37+
38+
[pos]
39+
embedded = false
40+
41+
[build]
42+
automatically_update_urls_on_dev = true
43+
include_config_on_deploy = true
44+
45+
[app_preferences]
46+
url = "https://example.com/preferences"

packages/e2e/setup/toml-app.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/* eslint-disable no-restricted-imports */
2+
import {authFixture} from './auth.js'
3+
import * as path from 'path'
4+
import * as fs from 'fs'
5+
import {fileURLToPath} from 'url'
6+
7+
const __filename = fileURLToPath(import.meta.url)
8+
const __dirname = path.dirname(__filename)
9+
10+
const FIXTURE_DIR = path.join(__dirname, '../data/valid-app')
11+
12+
/**
13+
* Test fixture that copies the full-toml fixture into a temp directory,
14+
* injects the real client_id, and exposes the path to tests.
15+
*/
16+
export const tomlAppFixture = authFixture.extend<{tomlAppDir: string}>({
17+
tomlAppDir: async ({env, authLogin: _authLogin}, use) => {
18+
const appDir = fs.mkdtempSync(path.join(env.tempDir, 'toml-app-'))
19+
20+
// Copy fixture files
21+
for (const file of fs.readdirSync(FIXTURE_DIR)) {
22+
fs.copyFileSync(path.join(FIXTURE_DIR, file), path.join(appDir, file))
23+
}
24+
25+
// Inject real client_id
26+
const tomlPath = path.join(appDir, 'shopify.app.toml')
27+
const toml = fs.readFileSync(tomlPath, 'utf8')
28+
fs.writeFileSync(tomlPath, toml.replace('__E2E_CLIENT_ID__', env.clientId))
29+
30+
await use(appDir)
31+
32+
fs.rmSync(appDir, {recursive: true, force: true})
33+
},
34+
})
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/* eslint-disable no-console */
2+
/* eslint-disable no-restricted-imports */
3+
import {authFixture as test} from '../setup/auth.js'
4+
import {requireEnv} from '../setup/env.js'
5+
import {expect} from '@playwright/test'
6+
import * as path from 'path'
7+
import * as fs from 'fs'
8+
import {fileURLToPath} from 'url'
9+
10+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
11+
const INVALID_TOMLS_DIR = path.join(__dirname, '../data/invalid-tomls')
12+
13+
const invalidTomls = fs.readdirSync(INVALID_TOMLS_DIR).filter((file) => file.endsWith('.toml'))
14+
15+
test.describe('TOML config invalid', () => {
16+
for (const tomlFile of invalidTomls) {
17+
const label = tomlFile.replace('.toml', '')
18+
19+
test(`deploy rejects invalid toml: ${label}`, async ({cli, env}) => {
20+
requireEnv(env, 'clientId')
21+
22+
// Set up temp dir with invalid toml + minimal package.json
23+
const appDir = fs.mkdtempSync(path.join(env.tempDir, `invalid-toml-${label}-`))
24+
try {
25+
const toml = fs
26+
.readFileSync(path.join(INVALID_TOMLS_DIR, tomlFile), 'utf8')
27+
.replace('__E2E_CLIENT_ID__', env.clientId)
28+
fs.writeFileSync(path.join(appDir, 'shopify.app.toml'), toml)
29+
fs.writeFileSync(
30+
path.join(appDir, 'package.json'),
31+
JSON.stringify({name: `invalid-${label}`, version: '1.0.0', private: true}),
32+
)
33+
34+
const result = await cli.exec(['app', 'deploy', '--path', appDir, '--force'], {
35+
timeout: 2 * 60 * 1000,
36+
})
37+
const output = result.stdout + result.stderr
38+
console.log(`[${label}] exit code: ${result.exitCode}\n[${label}] output:\n${output}`)
39+
expect(result.exitCode, `expected deploy to fail for ${label}, but it succeeded:\n${output}`).not.toBe(0)
40+
expect(output.toLowerCase(), `expected error output for ${label}:\n${output}`).toMatch(/error|invalid|failed/)
41+
} finally {
42+
fs.rmSync(appDir, {recursive: true, force: true})
43+
}
44+
})
45+
}
46+
})
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/* eslint-disable no-console */
2+
import {tomlAppFixture as test} from '../setup/toml-app.js'
3+
import {requireEnv} from '../setup/env.js'
4+
import {expect} from '@playwright/test'
5+
6+
test.describe('TOML config regression', () => {
7+
test('deploy succeeds with fully populated toml', async ({cli, env, tomlAppDir}) => {
8+
requireEnv(env, 'clientId')
9+
10+
const result = await cli.exec(['app', 'deploy', '--path', tomlAppDir, '--force'], {
11+
timeout: 5 * 60 * 1000,
12+
})
13+
const output = result.stdout + result.stderr
14+
expect(result.exitCode, `deploy failed:\n${output}`).toBe(0)
15+
})
16+
17+
test('dev starts with fully populated toml', async ({cli, env, tomlAppDir}) => {
18+
test.setTimeout(6 * 60 * 1000)
19+
requireEnv(env, 'clientId', 'storeFqdn')
20+
21+
const proc = await cli.spawn(['app', 'dev', '--path', tomlAppDir], {
22+
env: {CI: ''},
23+
})
24+
25+
try {
26+
await proc.waitForOutput('Ready, watching for changes in your app', 3 * 60 * 1000)
27+
28+
proc.sendKey('q')
29+
const exitCode = await proc.waitForExit(30_000)
30+
expect(exitCode, `dev exited with non-zero code. Output:\n${proc.getOutput()}`).toBe(0)
31+
} catch (error) {
32+
const captured = proc.getOutput()
33+
console.error(`[toml-config dev] Captured PTY output:\n${captured}`)
34+
throw error
35+
} finally {
36+
proc.kill()
37+
}
38+
})
39+
})

0 commit comments

Comments
 (0)