@essential-apps/shopify-test-runner 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contracts/normalize.d.ts +61 -0
- package/dist/contracts/normalize.d.ts.map +1 -0
- package/dist/contracts/normalize.js +99 -0
- package/dist/contracts/normalize.js.map +1 -0
- package/dist/contracts/normalizeHtml.d.ts +37 -0
- package/dist/contracts/normalizeHtml.d.ts.map +1 -0
- package/dist/contracts/normalizeHtml.js +89 -0
- package/dist/contracts/normalizeHtml.js.map +1 -0
- package/dist/edge/cert.d.ts +44 -0
- package/dist/edge/cert.d.ts.map +1 -0
- package/dist/edge/cert.js +117 -0
- package/dist/edge/cert.js.map +1 -0
- package/dist/edge/edgeProxy.d.ts +43 -0
- package/dist/edge/edgeProxy.d.ts.map +1 -0
- package/dist/edge/edgeProxy.js +297 -0
- package/dist/edge/edgeProxy.js.map +1 -0
- package/dist/edge/nodeShim.d.ts +2 -0
- package/dist/edge/nodeShim.d.ts.map +1 -0
- package/dist/edge/nodeShim.js +217 -0
- package/dist/edge/nodeShim.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/buildSourceBundle.d.ts +56 -0
- package/dist/lib/buildSourceBundle.d.ts.map +1 -0
- package/dist/lib/buildSourceBundle.js +153 -0
- package/dist/lib/buildSourceBundle.js.map +1 -0
- package/dist/lib/freePort.d.ts +5 -0
- package/dist/lib/freePort.d.ts.map +1 -0
- package/dist/lib/freePort.js +33 -0
- package/dist/lib/freePort.js.map +1 -0
- package/dist/lib/functionBuild.d.ts +99 -0
- package/dist/lib/functionBuild.d.ts.map +1 -0
- package/dist/lib/functionBuild.js +413 -0
- package/dist/lib/functionBuild.js.map +1 -0
- package/dist/lib/neonWsProxy.d.ts +41 -0
- package/dist/lib/neonWsProxy.d.ts.map +1 -0
- package/dist/lib/neonWsProxy.js +101 -0
- package/dist/lib/neonWsProxy.js.map +1 -0
- package/dist/lib/sourceZipUpload.d.ts +45 -0
- package/dist/lib/sourceZipUpload.d.ts.map +1 -0
- package/dist/lib/sourceZipUpload.js +129 -0
- package/dist/lib/sourceZipUpload.js.map +1 -0
- package/dist/lib/stealthLaunch.d.ts +35 -0
- package/dist/lib/stealthLaunch.d.ts.map +1 -0
- package/dist/lib/stealthLaunch.js +46 -0
- package/dist/lib/stealthLaunch.js.map +1 -0
- package/dist/lib/storeAutomation.d.ts +22 -0
- package/dist/lib/storeAutomation.d.ts.map +1 -0
- package/dist/lib/storeAutomation.js +85 -0
- package/dist/lib/storeAutomation.js.map +1 -0
- package/dist/playwright/baseConfig.d.ts +62 -0
- package/dist/playwright/baseConfig.d.ts.map +1 -0
- package/dist/playwright/baseConfig.js +68 -0
- package/dist/playwright/baseConfig.js.map +1 -0
- package/dist/playwright/globalSetup.d.ts +2 -0
- package/dist/playwright/globalSetup.d.ts.map +1 -0
- package/dist/playwright/globalSetup.js +139 -0
- package/dist/playwright/globalSetup.js.map +1 -0
- package/dist/playwright/index.d.ts +9 -0
- package/dist/playwright/index.d.ts.map +1 -0
- package/dist/playwright/index.js +9 -0
- package/dist/playwright/index.js.map +1 -0
- package/dist/probes/fonts.d.ts +4 -0
- package/dist/probes/fonts.d.ts.map +1 -0
- package/dist/probes/fonts.js +255 -0
- package/dist/probes/fonts.js.map +1 -0
- package/dist/probes/mirror.d.ts +4 -0
- package/dist/probes/mirror.d.ts.map +1 -0
- package/dist/probes/mirror.js +260 -0
- package/dist/probes/mirror.js.map +1 -0
- package/dist/probes/runProbe.d.ts +3 -0
- package/dist/probes/runProbe.d.ts.map +1 -0
- package/dist/probes/runProbe.js +219 -0
- package/dist/probes/runProbe.js.map +1 -0
- package/dist/probes/types.d.ts +72 -0
- package/dist/probes/types.d.ts.map +1 -0
- package/dist/probes/types.js +2 -0
- package/dist/probes/types.js.map +1 -0
- package/dist/scripts/_probeSourceUrl.d.ts +3 -0
- package/dist/scripts/_probeSourceUrl.d.ts.map +1 -0
- package/dist/scripts/_probeSourceUrl.js +119 -0
- package/dist/scripts/_probeSourceUrl.js.map +1 -0
- package/dist/scripts/addStore.d.ts +3 -0
- package/dist/scripts/addStore.d.ts.map +1 -0
- package/dist/scripts/addStore.js +46 -0
- package/dist/scripts/addStore.js.map +1 -0
- package/dist/scripts/buildDockerImage.d.ts +3 -0
- package/dist/scripts/buildDockerImage.d.ts.map +1 -0
- package/dist/scripts/buildDockerImage.js +60 -0
- package/dist/scripts/buildDockerImage.js.map +1 -0
- package/dist/scripts/captureAuth.d.ts +3 -0
- package/dist/scripts/captureAuth.d.ts.map +1 -0
- package/dist/scripts/captureAuth.js +124 -0
- package/dist/scripts/captureAuth.js.map +1 -0
- package/dist/scripts/captureContracts.d.ts +3 -0
- package/dist/scripts/captureContracts.d.ts.map +1 -0
- package/dist/scripts/captureContracts.js +517 -0
- package/dist/scripts/captureContracts.js.map +1 -0
- package/dist/scripts/captureRestContracts.d.ts +3 -0
- package/dist/scripts/captureRestContracts.d.ts.map +1 -0
- package/dist/scripts/captureRestContracts.js +245 -0
- package/dist/scripts/captureRestContracts.js.map +1 -0
- package/dist/scripts/checkOperationCoverage.d.ts +3 -0
- package/dist/scripts/checkOperationCoverage.d.ts.map +1 -0
- package/dist/scripts/checkOperationCoverage.js +302 -0
- package/dist/scripts/checkOperationCoverage.js.map +1 -0
- package/dist/scripts/cleanupStores.d.ts +3 -0
- package/dist/scripts/cleanupStores.d.ts.map +1 -0
- package/dist/scripts/cleanupStores.js +77 -0
- package/dist/scripts/cleanupStores.js.map +1 -0
- package/dist/scripts/createStores.d.ts +3 -0
- package/dist/scripts/createStores.d.ts.map +1 -0
- package/dist/scripts/createStores.js +66 -0
- package/dist/scripts/createStores.js.map +1 -0
- package/dist/scripts/deployAppVersion.d.ts +3 -0
- package/dist/scripts/deployAppVersion.d.ts.map +1 -0
- package/dist/scripts/deployAppVersion.js +591 -0
- package/dist/scripts/deployAppVersion.js.map +1 -0
- package/dist/scripts/devE2eBackend.d.ts +3 -0
- package/dist/scripts/devE2eBackend.d.ts.map +1 -0
- package/dist/scripts/devE2eBackend.js +117 -0
- package/dist/scripts/devE2eBackend.js.map +1 -0
- package/dist/scripts/devOnlineBackend.d.ts +3 -0
- package/dist/scripts/devOnlineBackend.d.ts.map +1 -0
- package/dist/scripts/devOnlineBackend.js +117 -0
- package/dist/scripts/devOnlineBackend.js.map +1 -0
- package/dist/scripts/installApp.d.ts +3 -0
- package/dist/scripts/installApp.d.ts.map +1 -0
- package/dist/scripts/installApp.js +163 -0
- package/dist/scripts/installApp.js.map +1 -0
- package/dist/scripts/listStores.d.ts +3 -0
- package/dist/scripts/listStores.d.ts.map +1 -0
- package/dist/scripts/listStores.js +18 -0
- package/dist/scripts/listStores.js.map +1 -0
- package/dist/scripts/runDocker.d.ts +3 -0
- package/dist/scripts/runDocker.d.ts.map +1 -0
- package/dist/scripts/runDocker.js +88 -0
- package/dist/scripts/runDocker.js.map +1 -0
- package/dist/scripts/runDockerAuth.d.ts +3 -0
- package/dist/scripts/runDockerAuth.d.ts.map +1 -0
- package/dist/scripts/runDockerAuth.js +108 -0
- package/dist/scripts/runDockerAuth.js.map +1 -0
- package/dist/scripts/runDockerOffline.d.ts +3 -0
- package/dist/scripts/runDockerOffline.d.ts.map +1 -0
- package/dist/scripts/runDockerOffline.js +129 -0
- package/dist/scripts/runDockerOffline.js.map +1 -0
- package/dist/scripts/runDockerOfflineExplore.d.ts +3 -0
- package/dist/scripts/runDockerOfflineExplore.d.ts.map +1 -0
- package/dist/scripts/runDockerOfflineExplore.js +116 -0
- package/dist/scripts/runDockerOfflineExplore.js.map +1 -0
- package/dist/scripts/runIsolatedDockerOffline.d.ts +3 -0
- package/dist/scripts/runIsolatedDockerOffline.d.ts.map +1 -0
- package/dist/scripts/runIsolatedDockerOffline.js +351 -0
- package/dist/scripts/runIsolatedDockerOffline.js.map +1 -0
- package/dist/scripts/runOffline.d.ts +3 -0
- package/dist/scripts/runOffline.d.ts.map +1 -0
- package/dist/scripts/runOffline.js +521 -0
- package/dist/scripts/runOffline.js.map +1 -0
- package/dist/scripts/runOfflineE2e.d.ts +3 -0
- package/dist/scripts/runOfflineE2e.d.ts.map +1 -0
- package/dist/scripts/runOfflineE2e.js +408 -0
- package/dist/scripts/runOfflineE2e.js.map +1 -0
- package/dist/scripts/runOfflineFullTests.d.ts +3 -0
- package/dist/scripts/runOfflineFullTests.d.ts.map +1 -0
- package/dist/scripts/runOfflineFullTests.js +1456 -0
- package/dist/scripts/runOfflineFullTests.js.map +1 -0
- package/dist/scripts/runSupermachine.d.ts +3 -0
- package/dist/scripts/runSupermachine.d.ts.map +1 -0
- package/dist/scripts/runSupermachine.js +474 -0
- package/dist/scripts/runSupermachine.js.map +1 -0
- package/dist/scripts/runSupermachineAuth.d.ts +3 -0
- package/dist/scripts/runSupermachineAuth.d.ts.map +1 -0
- package/dist/scripts/runSupermachineAuth.js +454 -0
- package/dist/scripts/runSupermachineAuth.js.map +1 -0
- package/dist/scripts/runTests.d.ts +3 -0
- package/dist/scripts/runTests.d.ts.map +1 -0
- package/dist/scripts/runTests.js +278 -0
- package/dist/scripts/runTests.js.map +1 -0
- package/dist/scripts/runVm.d.ts +3 -0
- package/dist/scripts/runVm.d.ts.map +1 -0
- package/dist/scripts/runVm.js +524 -0
- package/dist/scripts/runVm.js.map +1 -0
- package/dist/scripts/runVmAuth.d.ts +3 -0
- package/dist/scripts/runVmAuth.d.ts.map +1 -0
- package/dist/scripts/runVmAuth.js +475 -0
- package/dist/scripts/runVmAuth.js.map +1 -0
- package/dist/scripts/runVmScript.d.ts +3 -0
- package/dist/scripts/runVmScript.d.ts.map +1 -0
- package/dist/scripts/runVmScript.js +242 -0
- package/dist/scripts/runVmScript.js.map +1 -0
- package/dist/scripts/setupTestDb.d.ts +3 -0
- package/dist/scripts/setupTestDb.d.ts.map +1 -0
- package/dist/scripts/setupTestDb.js +61 -0
- package/dist/scripts/setupTestDb.js.map +1 -0
- package/dist/scripts/verifyContracts.d.ts +3 -0
- package/dist/scripts/verifyContracts.d.ts.map +1 -0
- package/dist/scripts/verifyContracts.js +258 -0
- package/dist/scripts/verifyContracts.js.map +1 -0
- package/dist/scripts/verifyRestContracts.d.ts +3 -0
- package/dist/scripts/verifyRestContracts.d.ts.map +1 -0
- package/dist/scripts/verifyRestContracts.js +237 -0
- package/dist/scripts/verifyRestContracts.js.map +1 -0
- package/dist/vite/offlineConfig.d.ts +34 -0
- package/dist/vite/offlineConfig.d.ts.map +1 -0
- package/dist/vite/offlineConfig.js +61 -0
- package/dist/vite/offlineConfig.js.map +1 -0
- package/dist/vite/onlineConfig.d.ts +42 -0
- package/dist/vite/onlineConfig.d.ts.map +1 -0
- package/dist/vite/onlineConfig.js +56 -0
- package/dist/vite/onlineConfig.js.map +1 -0
- package/docker/Dockerfile +67 -0
- package/docker/Dockerfile.vm +137 -0
- package/docker/README.md +50 -0
- package/docker/entrypoint.sh +198 -0
- package/package.json +85 -0
- package/src/contracts/normalize.ts +96 -0
- package/src/contracts/normalizeHtml.ts +98 -0
- package/src/edge/ca.cnf +14 -0
- package/src/edge/ca.crt +22 -0
- package/src/edge/ca.key +28 -0
- package/src/edge/cert.ts +117 -0
- package/src/edge/edgeProxy.ts +390 -0
- package/src/edge/server.cnf +28 -0
- package/src/edge/server.crt +26 -0
- package/src/edge/server.key +28 -0
- package/src/index.ts +67 -0
- package/src/lib/buildSourceBundle.ts +197 -0
- package/src/lib/freePort.ts +33 -0
- package/src/lib/functionBuild.ts +490 -0
- package/src/lib/neonWsProxy.ts +124 -0
- package/src/lib/sourceZipUpload.ts +168 -0
- package/src/lib/stealthLaunch.ts +57 -0
- package/src/lib/storeAutomation.ts +110 -0
- package/src/playwright/baseConfig.ts +120 -0
- package/src/playwright/globalSetup.ts +179 -0
- package/src/playwright/index.ts +11 -0
- package/src/probes/fonts.ts +279 -0
- package/src/probes/mirror.ts +283 -0
- package/src/probes/runProbe.ts +257 -0
- package/src/probes/types.ts +73 -0
- package/src/scripts/addStore.ts +59 -0
- package/src/scripts/buildDockerImage.ts +66 -0
- package/src/scripts/captureAuth.ts +145 -0
- package/src/scripts/captureContracts.ts +675 -0
- package/src/scripts/captureRestContracts.ts +319 -0
- package/src/scripts/checkOperationCoverage.ts +365 -0
- package/src/scripts/cleanupStores.ts +91 -0
- package/src/scripts/createStores.ts +77 -0
- package/src/scripts/deployAppVersion.ts +692 -0
- package/src/scripts/devOnlineBackend.ts +141 -0
- package/src/scripts/installApp.ts +188 -0
- package/src/scripts/listStores.ts +19 -0
- package/src/scripts/runDockerAuth.ts +120 -0
- package/src/scripts/runOffline.ts +577 -0
- package/src/scripts/runOfflineFullTests.ts +1634 -0
- package/src/scripts/runTests.ts +306 -0
- package/src/scripts/runVm.ts +562 -0
- package/src/scripts/runVmAuth.ts +541 -0
- package/src/scripts/runVmScript.ts +282 -0
- package/src/scripts/setupTestDb.ts +71 -0
- package/src/scripts/verifyContracts.ts +310 -0
- package/src/scripts/verifyRestContracts.ts +275 -0
- package/src/vite/onlineConfig.ts +60 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Run the in-container online suite: spin up Postgres + Vite + Playwright.
|
|
4
|
+
*
|
|
5
|
+
* This script is invoked INSIDE the test container by `runDocker.ts`.
|
|
6
|
+
* It can also be run on the host if you have a local Postgres at
|
|
7
|
+
* 127.0.0.1:5432 and Chrome installed (matches `npm run test:online` in the
|
|
8
|
+
* consuming app).
|
|
9
|
+
*
|
|
10
|
+
* Per-run isolation is via a UUID-named Postgres database. The DB is
|
|
11
|
+
* created at startup, migrated, used by the spawned Vite backend, and
|
|
12
|
+
* dropped on exit (unless TEST_PRESERVE_DB=true).
|
|
13
|
+
*
|
|
14
|
+
* Conventions (overridable via env, but Phase 0 just hardcodes them):
|
|
15
|
+
* - Backend: `npx vite --port <port> --strictPort --host 0.0.0.0`
|
|
16
|
+
* - Schema migration: `npx prisma migrate deploy`
|
|
17
|
+
* - Playwright config at `tests/test-online/playwright.config.ts`
|
|
18
|
+
* - DB name prefix: derived from the consuming app's package name
|
|
19
|
+
*
|
|
20
|
+
* Env vars forwarded into the backend process (hermetic — we do NOT
|
|
21
|
+
* inherit Shopify-related secrets from process.env beyond these):
|
|
22
|
+
* - PATH, HOME, USER (process basics)
|
|
23
|
+
* - DATABASE_URL, DIRECT_URL (per-run UUID DB)
|
|
24
|
+
* - SHOPIFY_API_KEY, SHOPIFY_API_SECRET, SCOPES (from .env.test)
|
|
25
|
+
* - SHOPIFY_APP_URL, PORT, HOST (from .env.test, derived)
|
|
26
|
+
* - SHOPIFY_UPSELLS_ID (if set; specific to apps with theme app
|
|
27
|
+
* extensions — harmless no-op otherwise)
|
|
28
|
+
*/
|
|
29
|
+
import { spawn, execSync, type ChildProcess } from 'node:child_process';
|
|
30
|
+
import { randomUUID } from 'node:crypto';
|
|
31
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
32
|
+
import { resolve } from 'node:path';
|
|
33
|
+
import { fileURLToPath } from 'node:url';
|
|
34
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
35
|
+
import { Agent } from 'undici';
|
|
36
|
+
import { loadEnv, printEnvSummary } from '@essential-apps/shopify-test-core';
|
|
37
|
+
import { isPortFree } from '../lib/freePort.js';
|
|
38
|
+
|
|
39
|
+
// Vite serves a self-signed cert in test mode — node:fetch rejects
|
|
40
|
+
// these by default. Use a permissive dispatcher just for the
|
|
41
|
+
// readiness probe.
|
|
42
|
+
const insecureFetchDispatcher = new Agent({ connect: { rejectUnauthorized: false } });
|
|
43
|
+
|
|
44
|
+
const PG_HOST = 'localhost';
|
|
45
|
+
const PG_PORT = '5432';
|
|
46
|
+
const READY_TIMEOUT_MS = 120_000;
|
|
47
|
+
|
|
48
|
+
interface PackageJson {
|
|
49
|
+
name?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function readAppName(): string {
|
|
53
|
+
const pkgPath = resolve(process.cwd(), 'package.json');
|
|
54
|
+
if (!existsSync(pkgPath)) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`No package.json at ${pkgPath}. Run this from the consuming app's repo root.`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as PackageJson;
|
|
60
|
+
if (!pkg.name) throw new Error(`package.json at ${pkgPath} has no \`name\` field.`);
|
|
61
|
+
return pkg.name.replace(/^@[^/]+\//, '');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function dbUrl(name: string): string {
|
|
65
|
+
// process.env.USER is empty in some container runtimes; PGUSER
|
|
66
|
+
// override wins. Last-resort fallback to 'root' (the container's
|
|
67
|
+
// default user).
|
|
68
|
+
const user = process.env['PGUSER'] || process.env['USER'] || 'root';
|
|
69
|
+
// Prisma's default connection_limit is `num_physical_cpus * 2 + 1`
|
|
70
|
+
// (commonly 9). Apps that fan out parallel queries on boot (Shopify
|
|
71
|
+
// app-remix's session-storage-prisma validating sessions, etc.)
|
|
72
|
+
// can saturate that pool — especially when each engine call is
|
|
73
|
+
// slowed by amd64 Rosetta translation inside the VM, holding
|
|
74
|
+
// connections longer. An exhausted pool throws
|
|
75
|
+
// PrismaClientInitializationError, which crashes Vite/Remix
|
|
76
|
+
// (unhandled rejection → process exit) and takes down the cleanup
|
|
77
|
+
// endpoint along with it. 25 is generous enough to handle test-time
|
|
78
|
+
// burstiness without being suspiciously high in production logs
|
|
79
|
+
// (Vite never actually opens that many — it's a ceiling).
|
|
80
|
+
// pool_timeout=20 (default 10) gives slow Rosetta engine starts a
|
|
81
|
+
// bigger window before they're considered "timed out".
|
|
82
|
+
return `postgresql://${user}@${PG_HOST}:${PG_PORT}/${name}?connection_limit=25&pool_timeout=20`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function waitForReady(url: string, timeoutMs: number): Promise<void> {
|
|
86
|
+
const deadline = Date.now() + timeoutMs;
|
|
87
|
+
let lastErr = '';
|
|
88
|
+
while (Date.now() < deadline) {
|
|
89
|
+
try {
|
|
90
|
+
const res = await fetch(url, {
|
|
91
|
+
redirect: 'manual',
|
|
92
|
+
// Cast: node fetch types omit dispatcher even though it's supported
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
94
|
+
dispatcher: insecureFetchDispatcher as any,
|
|
95
|
+
} as RequestInit);
|
|
96
|
+
if (res.status > 0) return;
|
|
97
|
+
} catch (e) {
|
|
98
|
+
lastErr = (e as Error).message;
|
|
99
|
+
}
|
|
100
|
+
await sleep(2_000);
|
|
101
|
+
}
|
|
102
|
+
throw new Error(`Backend never became ready at ${url}. Last error: ${lastErr}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function main(): Promise<void> {
|
|
106
|
+
const env = loadEnv();
|
|
107
|
+
printEnvSummary(env);
|
|
108
|
+
|
|
109
|
+
if (!env.apiSecret) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`SHOPIFY_API_SECRET is not set in .env.test.\n\n` +
|
|
112
|
+
`Get it from Partner Dashboard → Apps → "${env.devAppHandle}" → ` +
|
|
113
|
+
`Configuration → "Client credentials" → "Reveal client secret".\n` +
|
|
114
|
+
`Paste it into .env.test as SHOPIFY_API_SECRET=...`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// localhost mode: SHOPIFY_APP_URL must be a https://localhost:PORT
|
|
119
|
+
// URL. Vite serves a self-signed cert via @vitejs/plugin-basic-ssl.
|
|
120
|
+
// The browser embeds the iframe at that URL — Playwright is
|
|
121
|
+
// configured with ignoreHTTPSErrors so the cert is accepted.
|
|
122
|
+
const appUrl = new URL(env.devAppUrl);
|
|
123
|
+
if (appUrl.hostname !== 'localhost' && appUrl.hostname !== '127.0.0.1') {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`SHOPIFY_APP_URL must be a localhost URL (https://localhost:PORT). ` +
|
|
126
|
+
`Got: ${env.devAppUrl}\n` +
|
|
127
|
+
`This setup uses Vite's self-signed cert in test mode (no tunnel).`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
if (appUrl.protocol !== 'https:') {
|
|
131
|
+
throw new Error(`SHOPIFY_APP_URL must use https:// — got ${env.devAppUrl}`);
|
|
132
|
+
}
|
|
133
|
+
const port = appUrl.port || '443';
|
|
134
|
+
|
|
135
|
+
// Pre-flight: refuse to run if the port is already in use. Avoids
|
|
136
|
+
// false-positive runs where Vite would silently take a different port.
|
|
137
|
+
if (!(await isPortFree(Number(port)))) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Port ${port} is in use, refusing to start.\n` +
|
|
140
|
+
`Find what's holding it: lsof -i :${port}\n` +
|
|
141
|
+
`Then either free it, or change SHOPIFY_APP_URL in .env.test ` +
|
|
142
|
+
`to a different port (and update Partner Dashboard accordingly).`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const appName = readAppName();
|
|
147
|
+
// Postgres identifier rules: lowercase, underscores. Sanitize.
|
|
148
|
+
const dbPrefix = (process.env['TEST_ONLINE_DB_NAME_PREFIX'] ?? `${appName}_online`)
|
|
149
|
+
.toLowerCase()
|
|
150
|
+
.replace(/[^a-z0-9_]/g, '_');
|
|
151
|
+
const dbName = `${dbPrefix}_${randomUUID().replace(/-/g, '_')}`;
|
|
152
|
+
const url = dbUrl(dbName);
|
|
153
|
+
const preserveDb = process.env['TEST_PRESERVE_DB'] === 'true';
|
|
154
|
+
|
|
155
|
+
console.log(`[runTests] Test DB: ${url}`);
|
|
156
|
+
console.log(`[runTests] Backend: ${env.devAppUrl} (vite https, self-signed cert)`);
|
|
157
|
+
console.log('');
|
|
158
|
+
|
|
159
|
+
console.log('[runTests] createdb…');
|
|
160
|
+
execSync(`createdb -h ${PG_HOST} -p ${PG_PORT} ${dbName}`, { stdio: 'inherit' });
|
|
161
|
+
|
|
162
|
+
// Per-run secret for the test-internal store-cleanup endpoint.
|
|
163
|
+
// Generated fresh each run so we never depend on a long-lived
|
|
164
|
+
// shared secret in .env.test (which used to point at a personal
|
|
165
|
+
// dev tunnel and broke for any other dev / CI). Both the backend
|
|
166
|
+
// (validates) and the test process (sends header) see the same
|
|
167
|
+
// value via backendEnv below.
|
|
168
|
+
const cleanupSecret = randomUUID();
|
|
169
|
+
// Backend serves the cleanup endpoint at /test-internal/cleanup-shop.
|
|
170
|
+
// Reach it via the same localhost URL the test browser uses for
|
|
171
|
+
// the embedded app — no external tunnel involved.
|
|
172
|
+
const cleanupUrl = `${env.devAppUrl.replace(/\/$/, '')}/test-internal/cleanup-shop`;
|
|
173
|
+
|
|
174
|
+
// Hermetic env for the spawned Vite + Playwright. We do NOT inherit
|
|
175
|
+
// Shopify-related secrets from process.env — we hand-build them
|
|
176
|
+
// from .env.test only.
|
|
177
|
+
const backendEnv: NodeJS.ProcessEnv = {
|
|
178
|
+
PATH: process.env['PATH'] ?? '',
|
|
179
|
+
// See runOfflineFullTests.ts for the long-form note — empty HOME
|
|
180
|
+
// makes npm write its cache to literal `~/.npm` in cwd.
|
|
181
|
+
HOME: process.env['HOME'] || '/root',
|
|
182
|
+
USER: process.env['USER'] ?? '',
|
|
183
|
+
NODE_ENV: 'test',
|
|
184
|
+
DATABASE_URL: url,
|
|
185
|
+
DIRECT_URL: url,
|
|
186
|
+
SHOPIFY_API_KEY: env.devClientId,
|
|
187
|
+
SHOPIFY_API_SECRET: env.apiSecret,
|
|
188
|
+
SCOPES: env.scopes,
|
|
189
|
+
SHOPIFY_APP_URL: env.devAppUrl,
|
|
190
|
+
PORT: port,
|
|
191
|
+
HOST: env.devAppUrl,
|
|
192
|
+
// Store-cleanup wiring (see storePool.ts afterEach). Both the
|
|
193
|
+
// backend route handler (validates X-Test-Cleanup-Secret) and the
|
|
194
|
+
// Playwright test process (sends the header) read these.
|
|
195
|
+
TEST_ONLINE_CLEANUP_SECRET: cleanupSecret,
|
|
196
|
+
TEST_ONLINE_STORE_CLEANUP_URL: cleanupUrl,
|
|
197
|
+
// Theme app extension UUID — relevant for apps with theme app
|
|
198
|
+
// extensions (essential-upsell). Without this, vite/remix loads
|
|
199
|
+
// the dev-app value from .env (since vite/remix auto-loads .env
|
|
200
|
+
// even with NODE_ENV=test), so the Activate deep link points at
|
|
201
|
+
// the wrong extension and isAppEmbedActive never matches.
|
|
202
|
+
// Harmless no-op for apps that don't use this var.
|
|
203
|
+
...(process.env['SHOPIFY_UPSELLS_ID']
|
|
204
|
+
? { SHOPIFY_UPSELLS_ID: process.env['SHOPIFY_UPSELLS_ID'] }
|
|
205
|
+
: {}),
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
console.log('[runTests] prisma migrate deploy…');
|
|
209
|
+
execSync('npx prisma migrate deploy', { stdio: 'inherit', env: backendEnv });
|
|
210
|
+
|
|
211
|
+
let dev: ChildProcess | null = null;
|
|
212
|
+
|
|
213
|
+
const killBackend = (): void => {
|
|
214
|
+
if (dev && !dev.killed) {
|
|
215
|
+
try {
|
|
216
|
+
dev.kill('SIGTERM');
|
|
217
|
+
} catch {
|
|
218
|
+
// ignore
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
process.on('SIGINT', killBackend);
|
|
223
|
+
process.on('SIGTERM', killBackend);
|
|
224
|
+
|
|
225
|
+
let exitCode = 1;
|
|
226
|
+
try {
|
|
227
|
+
console.log('[runTests] booting Remix dev server (vite https, no tunnel, no Shopify CLI)…');
|
|
228
|
+
console.log('');
|
|
229
|
+
|
|
230
|
+
// Serve via Vite with the online-suite config overlay
|
|
231
|
+
// (viteOnlineConfig): it loads the app's OWN vite config and adds
|
|
232
|
+
// optimizeDeps.entries so every route's deps are pre-bundled at
|
|
233
|
+
// server startup. Without it, Vite discovers a route's deps on the
|
|
234
|
+
// first navigation, re-optimizes, and force-reloads the embedded
|
|
235
|
+
// iframe mid-test (the funnels "placement type list" flake). The
|
|
236
|
+
// overlay is online-only and additive — it never touches the app's
|
|
237
|
+
// own dev/build. Opt out with TEST_ONLINE_VITE_OPTIMIZE_ALL=0 →
|
|
238
|
+
// bare `vite` with the app config unchanged.
|
|
239
|
+
const viteArgs = ['vite', '--port', port, '--strictPort', '--host', '0.0.0.0'];
|
|
240
|
+
if (process.env['TEST_ONLINE_VITE_OPTIMIZE_ALL'] !== '0') {
|
|
241
|
+
viteArgs.push('--config', fileURLToPath(new URL('../vite/onlineConfig.js', import.meta.url)));
|
|
242
|
+
}
|
|
243
|
+
dev = spawn('npx', viteArgs, {
|
|
244
|
+
stdio: 'inherit',
|
|
245
|
+
env: backendEnv,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
console.log(`[runTests] waiting for backend at ${env.devAppUrl}…`);
|
|
249
|
+
await waitForReady(env.devAppUrl, READY_TIMEOUT_MS);
|
|
250
|
+
console.log('[runTests] backend ready.');
|
|
251
|
+
console.log('');
|
|
252
|
+
|
|
253
|
+
console.log('[runTests] running Playwright…');
|
|
254
|
+
// Playwright test process needs more env than the backend — it
|
|
255
|
+
// reads TEST_ONLINE_ACCOUNT_EMAIL, SHOPIFY_APP_HANDLE, etc. from
|
|
256
|
+
// process.env. The backend gets the hermetic backendEnv;
|
|
257
|
+
// Playwright gets backendEnv + the .env.test metadata that's
|
|
258
|
+
// already in process.env (via the --env-file=.env.test flag on
|
|
259
|
+
// the runner).
|
|
260
|
+
const playwrightConfig =
|
|
261
|
+
process.env['TEST_PLAYWRIGHT_CONFIG'] ?? 'tests/test-online/playwright.config.ts';
|
|
262
|
+
// PLAYWRIGHT_GREP narrows the run to tests matching a regex
|
|
263
|
+
// (Playwright's --grep). Documented as the env-var entry point in
|
|
264
|
+
// runOffline.ts; mirror it here so the vm path
|
|
265
|
+
// (which goes through runVm.ts → runTests.ts) honors it
|
|
266
|
+
// too. Without this, an env var that "works in offline mode"
|
|
267
|
+
// silently runs the whole suite under the vm.
|
|
268
|
+
const grep = process.env['PLAYWRIGHT_GREP'];
|
|
269
|
+
const grepArg = grep ? ` --grep ${JSON.stringify(grep)}` : '';
|
|
270
|
+
execSync(`npx playwright test --config ${playwrightConfig}${grepArg}`, {
|
|
271
|
+
stdio: 'inherit',
|
|
272
|
+
env: {
|
|
273
|
+
...process.env,
|
|
274
|
+
...backendEnv,
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
exitCode = 0;
|
|
278
|
+
} catch (err) {
|
|
279
|
+
console.error(`[runTests] failed: ${(err as Error).message}`);
|
|
280
|
+
} finally {
|
|
281
|
+
console.log('');
|
|
282
|
+
console.log('[runTests] killing backend…');
|
|
283
|
+
killBackend();
|
|
284
|
+
await sleep(2_000);
|
|
285
|
+
|
|
286
|
+
if (preserveDb) {
|
|
287
|
+
console.log(`[runTests] DB preserved for inspection: ${dbName}`);
|
|
288
|
+
console.log(` Connect: psql ${url}`);
|
|
289
|
+
console.log(` Drop: dropdb -h ${PG_HOST} -p ${PG_PORT} ${dbName}`);
|
|
290
|
+
} else {
|
|
291
|
+
try {
|
|
292
|
+
execSync(`dropdb -h ${PG_HOST} -p ${PG_PORT} ${dbName}`, { stdio: 'inherit' });
|
|
293
|
+
console.log(`[runTests] dropped DB: ${dbName}`);
|
|
294
|
+
} catch {
|
|
295
|
+
console.warn(`[runTests] could not drop DB (still in use?): ${dbName}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
process.exit(exitCode);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
main().catch((err) => {
|
|
304
|
+
console.error(err);
|
|
305
|
+
process.exit(1);
|
|
306
|
+
});
|