@essential-apps/shopify-test-runner 1.0.27 → 1.0.29

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.
Files changed (34) hide show
  1. package/dist/edge/edgeProxy.d.ts +2 -0
  2. package/dist/edge/edgeProxy.d.ts.map +1 -1
  3. package/dist/edge/edgeProxy.js +10 -1
  4. package/dist/edge/edgeProxy.js.map +1 -1
  5. package/dist/lib/neonWsProxy.d.ts +1 -1
  6. package/dist/lib/neonWsProxy.js +1 -1
  7. package/dist/playwright/baseConfig.d.ts +1 -1
  8. package/dist/scripts/devOnlineBackend.js +1 -1
  9. package/dist/scripts/devOnlineBackend.js.map +1 -1
  10. package/dist/scripts/runOffline.js +12 -12
  11. package/dist/scripts/runOffline.js.map +1 -1
  12. package/dist/scripts/runOfflineTests.d.ts +3 -0
  13. package/dist/scripts/runOfflineTests.d.ts.map +1 -0
  14. package/dist/scripts/{runOfflineFullTests.js → runOfflineTests.js} +80 -81
  15. package/dist/scripts/runOfflineTests.js.map +1 -0
  16. package/dist/scripts/runTests.js +1 -1
  17. package/dist/scripts/runTests.js.map +1 -1
  18. package/dist/scripts/runVm.js +2 -2
  19. package/dist/scripts/runVm.js.map +1 -1
  20. package/dist/vite/onlineConfig.d.ts +1 -1
  21. package/dist/vite/onlineConfig.js +1 -1
  22. package/package.json +1 -1
  23. package/src/edge/edgeProxy.ts +13 -1
  24. package/src/lib/neonWsProxy.ts +1 -1
  25. package/src/playwright/baseConfig.ts +1 -1
  26. package/src/scripts/devOnlineBackend.ts +1 -1
  27. package/src/scripts/runOffline.ts +12 -12
  28. package/src/scripts/{runOfflineFullTests.ts → runOfflineTests.ts} +79 -80
  29. package/src/scripts/runTests.ts +1 -1
  30. package/src/scripts/runVm.ts +2 -2
  31. package/src/vite/onlineConfig.ts +1 -1
  32. package/dist/scripts/runOfflineFullTests.d.ts +0 -3
  33. package/dist/scripts/runOfflineFullTests.d.ts.map +0 -1
  34. package/dist/scripts/runOfflineFullTests.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * runOfflineFullTests — full-stack offline orchestrator.
3
+ * runOfflineTests — full-stack offline orchestrator.
4
4
  *
5
5
  * Boots the entire offline stack in ONE Node process:
6
6
  *
@@ -13,14 +13,14 @@
13
13
  * reaches our Admin GraphQL mock
14
14
  * - Playwright as a child process, with mock URLs in env
15
15
  *
16
- * The Playwright fixture (`offlineFullTest`, orchestrated mode)
16
+ * The Playwright fixture (`offlineTest`, orchestrated mode)
17
17
  * reads the mock URLs from env and routes browser requests via
18
18
  * `page.route()`. Tests mutate ShopState via control-plane HTTP
19
19
  * endpoints exposed by the storefront mock (mounted under
20
20
  * `/__test__/state/*`).
21
21
  *
22
22
  * This is the offline counterpart to runTests.ts (online mode).
23
- * Online tests still go via runTests.ts; offline-full tests via this.
23
+ * Online tests still go via runTests.ts; offline tests via this.
24
24
  *
25
25
  * Env it sets in the spawned Remix backend:
26
26
  * SHOPIFY_API_SECRET = mock JWT secret (so JWTs validate)
@@ -135,7 +135,7 @@ function dropOrphanDatabases(prefix: string): void {
135
135
  .map((s) => s.trim())
136
136
  .filter((s) => s.length > 0);
137
137
  if (orphans.length === 0) return;
138
- console.log(`[runOfflineFull] cleanup: dropping ${orphans.length} orphan DB(s)…`);
138
+ console.log(`[runOfflineTests] cleanup: dropping ${orphans.length} orphan DB(s)…`);
139
139
  for (const name of orphans) {
140
140
  try {
141
141
  // `--force` (psql 13+) terminates any leftover connections
@@ -152,7 +152,7 @@ function dropOrphanDatabases(prefix: string): void {
152
152
  } catch (err) {
153
153
  // If psql isn't available at all we just skip cleanup; the
154
154
  // primary createdb below will fail loudly anyway.
155
- console.warn(`[runOfflineFull] orphan-DB cleanup failed: ${(err as Error).message}`);
155
+ console.warn(`[runOfflineTests] orphan-DB cleanup failed: ${(err as Error).message}`);
156
156
  }
157
157
  }
158
158
 
@@ -289,7 +289,7 @@ function ensureOfflineSetup(): void {
289
289
  try {
290
290
  hostsContent = readFileSync(hostsPath, 'utf8');
291
291
  } catch {
292
- console.warn(`[runOfflineFull] could not read ${hostsPath}; skipping /etc/hosts setup`);
292
+ console.warn(`[runOfflineTests] could not read ${hostsPath}; skipping /etc/hosts setup`);
293
293
  return;
294
294
  }
295
295
  const hostsToAdd: string[] = [];
@@ -302,6 +302,10 @@ function ensureOfflineSetup(): void {
302
302
  // `font_face` filter output (which contains real
303
303
  // fonts.shopifycdn.com URLs) just works in-browser.
304
304
  'fonts.shopifycdn.com',
305
+ // Partner API — so in-VM Node test code (the issue-credit helper)
306
+ // reaches the mock through the edge proxy, same as the backend's
307
+ // hard-coded https://partners.shopify.com fetch. No app-side override.
308
+ 'partners.shopify.com',
305
309
  ]) {
306
310
  if (!new RegExp(`^127\\.0\\.0\\.1\\s+${host.replace(/\./g, '\\.')}\\s*$`, 'm').test(hostsContent)) {
307
311
  hostsToAdd.push(`127.0.0.1 ${host}`);
@@ -312,10 +316,10 @@ function ensureOfflineSetup(): void {
312
316
  if (hostsToAdd.length > 0) {
313
317
  try {
314
318
  writeFileSync(hostsPath, hostsContent + '\n' + hostsToAdd.join('\n') + '\n');
315
- console.log(`[runOfflineFull] wrote ${hostsToAdd.length} /etc/hosts entries`);
319
+ console.log(`[runOfflineTests] wrote ${hostsToAdd.length} /etc/hosts entries`);
316
320
  } catch (err) {
317
321
  console.warn(
318
- `[runOfflineFull] could not write ${hostsPath}: ${(err as Error).message}. ` +
322
+ `[runOfflineTests] could not write ${hostsPath}: ${(err as Error).message}. ` +
319
323
  `Browser may resolve Shopify hostnames to real Shopify on the internet.`,
320
324
  );
321
325
  }
@@ -336,12 +340,12 @@ function ensureOfflineSetup(): void {
336
340
  if (existing !== TEST_CA_CERT_PEM) {
337
341
  writeFileSync(systemCertDest, TEST_CA_CERT_PEM);
338
342
  execSync('update-ca-certificates >/dev/null 2>&1 || true', { stdio: 'ignore' });
339
- console.log('[runOfflineFull] installed edge CA into system trust store');
343
+ console.log('[runOfflineTests] installed edge CA into system trust store');
340
344
  }
341
345
  process.env['NODE_EXTRA_CA_CERTS'] = systemCertDest;
342
346
  } catch (err) {
343
347
  console.warn(
344
- `[runOfflineFull] could not install edge CA: ${(err as Error).message}. ` +
348
+ `[runOfflineTests] could not install edge CA: ${(err as Error).message}. ` +
345
349
  `TLS connections to the edge may fail.`,
346
350
  );
347
351
  }
@@ -381,10 +385,10 @@ function ensureOfflineSetup(): void {
381
385
  { stdio: 'ignore' },
382
386
  );
383
387
  unlinkSync(tmpCaPath);
384
- console.log('[runOfflineFull] installed edge CA into NSS DB (Chrome)');
388
+ console.log('[runOfflineTests] installed edge CA into NSS DB (Chrome)');
385
389
  } catch (err) {
386
390
  console.warn(
387
- `[runOfflineFull] could not install edge CA into NSS DB: ${(err as Error).message}. ` +
391
+ `[runOfflineTests] could not install edge CA into NSS DB: ${(err as Error).message}. ` +
388
392
  `Chrome will reject the edge's cert. Verify libnss3-tools is installed in the image.`,
389
393
  );
390
394
  }
@@ -421,7 +425,7 @@ async function startOfflineDnsRedirect(): Promise<OfflineResolver | null> {
421
425
  resolver = await startOfflineResolver({ upstream });
422
426
  } catch (err) {
423
427
  console.warn(
424
- `[runOfflineFull] offline DNS resolver failed to bind 127.0.0.1:53: ` +
428
+ `[runOfflineTests] offline DNS resolver failed to bind 127.0.0.1:53: ` +
425
429
  `${(err as Error).message}. page.request may resolve Shopify ` +
426
430
  `hostnames to real Shopify on the internet.`,
427
431
  );
@@ -437,12 +441,12 @@ async function startOfflineDnsRedirect(): Promise<OfflineResolver | null> {
437
441
  // so an aggressive timeout would spuriously fail real lookups.
438
442
  writeFileSync('/etc/resolv.conf', 'nameserver 127.0.0.1\n');
439
443
  console.log(
440
- '[runOfflineFull] offline DNS resolver on 127.0.0.1:53 ' +
444
+ '[runOfflineTests] offline DNS resolver on 127.0.0.1:53 ' +
441
445
  `(Shopify → loopback${upstream ? `, else → ${upstream}` : ', else → SERVFAIL'})`,
442
446
  );
443
447
  } catch (err) {
444
448
  console.warn(
445
- `[runOfflineFull] could not repoint /etc/resolv.conf: ` +
449
+ `[runOfflineTests] could not repoint /etc/resolv.conf: ` +
446
450
  `${(err as Error).message}. c-ares clients may bypass the resolver.`,
447
451
  );
448
452
  }
@@ -548,7 +552,7 @@ function seedSession(env: Record<string, string>, shopDomain: string): void {
548
552
  * - Supervisor (no `TEST_OFFLINE_SHARD_INDEX` set, N>1):
549
553
  * 1. Build cache check + build once (so shards don't race)
550
554
  * 2. Orphan-DB cleanup once
551
- * 3. Spawn N child processes (`node ... runOfflineFullTests.ts`)
555
+ * 3. Spawn N child processes (`node ... runOfflineTests.ts`)
552
556
  * each with `TEST_OFFLINE_SHARD_INDEX=k`, `TEST_OFFLINE_SKIP_BUILD=true`
553
557
  * 4. Stream child stdout/stderr prefixed with [shard k]
554
558
  * 5. Wait for all, exit with worst exit code
@@ -580,14 +584,14 @@ async function main(): Promise<void> {
580
584
  // strategy or invasive Chromium/Node bypasses. We tried that path
581
585
  // (page.route() + host-resolver-rules + a Node socket-layer shim);
582
586
  // it worked but maintained ~400 lines of bypass code in parallel.
583
- // Use `npm run test:offline-full` from the consuming app — it
587
+ // Use `npm run test:offline` from the consuming app — it
584
588
  // routes through `runDockerOffline.ts` which spawns the
585
589
  // `essential-upsell-test:latest` image where TEST_IN_CONTAINER=true
586
590
  // is set by docker/entrypoint.sh.
587
591
  if (process.env['TEST_IN_CONTAINER'] !== 'true') {
588
592
  console.error(
589
- '[runOfflineFull] FATAL: must run inside the offline container.\n' +
590
- ' Run: `npm run test:offline-full` (which invokes runDockerOffline.ts)\n' +
593
+ '[runOfflineTests] FATAL: must run inside the offline container.\n' +
594
+ ' Run: `npm run test:offline` (which invokes runDockerOffline.ts)\n' +
591
595
  ' If you are seeing this from inside what should be a container, the\n' +
592
596
  " entrypoint script (tests/offline/docker/entrypoint.sh) didn't run or\n" +
593
597
  " didn't export TEST_IN_CONTAINER=true.",
@@ -642,7 +646,7 @@ async function main(): Promise<void> {
642
646
  const scopes = process.env['SCOPES'] ?? 'write_products,read_products,write_discounts,read_discounts';
643
647
  const playwrightConfig =
644
648
  process.env['TEST_PLAYWRIGHT_CONFIG'] ??
645
- 'tests/offline/playwright.offline-full.config.ts';
649
+ 'tests/offline/playwright.offline.config.ts';
646
650
  const isChildShard = shardIndex !== null;
647
651
  // Backend port: shard k uses BASE + (k-1); single-worker uses BASE.
648
652
  const baseBackendPort = Number(process.env['TEST_OFFLINE_BACKEND_PORT'] ?? '8181');
@@ -650,9 +654,9 @@ async function main(): Promise<void> {
650
654
  isChildShard ? baseBackendPort + (shardIndex - 1) : baseBackendPort,
651
655
  );
652
656
  // Production Remix serves via plain HTTP (remix-serve has no TLS).
653
- // Vite dev had self-signed HTTPS; offline-full uses HTTP. The mock
657
+ // Vite dev had self-signed HTTPS; offline uses HTTP. The mock
654
658
  // admin shell serves the iframe pointing here; mixed-content
655
- // warnings are silenced via Chrome flags in the offlineFullStack
659
+ // warnings are silenced via Chrome flags in the offlineStack
656
660
  // fixture.
657
661
  const backendUrl = `http://localhost:${backendPort}`;
658
662
  // Skip the build if TEST_OFFLINE_SKIP_BUILD=true and a recent build
@@ -667,8 +671,8 @@ async function main(): Promise<void> {
667
671
  const dbConn = dbUrl(dbName);
668
672
  const preserveDb = process.env['TEST_PRESERVE_DB'] === 'true';
669
673
 
670
- console.log(`[runOfflineFull] Test DB: ${dbConn}`);
671
- console.log(`[runOfflineFull] Backend: ${backendUrl}`);
674
+ console.log(`[runOfflineTests] Test DB: ${dbConn}`);
675
+ console.log(`[runOfflineTests] Backend: ${backendUrl}`);
672
676
 
673
677
  // Drop orphan DBs from previous crashed runs. The supervisor
674
678
  // already did this once — children skip.
@@ -676,7 +680,7 @@ async function main(): Promise<void> {
676
680
  dropOrphanDatabases(dbPrefix);
677
681
  }
678
682
 
679
- console.log('[runOfflineFull] createdb…');
683
+ console.log('[runOfflineTests] createdb…');
680
684
  execSync(`createdb -h ${PG_HOST} -p ${PG_PORT} ${dbName}`, { stdio: 'inherit' });
681
685
 
682
686
  // ── Mock servers (in-process, sharing one ShopState) ─────
@@ -702,7 +706,7 @@ async function main(): Promise<void> {
702
706
  seedExploreProducts(state);
703
707
  }
704
708
 
705
- console.log('[runOfflineFull] booting mock servers…');
709
+ console.log('[runOfflineTests] booting mock servers…');
706
710
  // 127.0.0.1 binding is the safe default — orchestrator and child
707
711
  // both reach the mocks on loopback. Used to be subtly broken
708
712
  // because we ran Playwright via execSync (sync, blocks the event
@@ -732,7 +736,7 @@ async function main(): Promise<void> {
732
736
  : undefined;
733
737
  if (postPurchaseExtensionPath && !existsSync(postPurchaseExtensionPath)) {
734
738
  throw new Error(
735
- `[runOfflineFull] TEST_OFFLINE_POST_PURCHASE_EXT points at ${postPurchaseExtensionPath} but the file doesn't exist. ` +
739
+ `[runOfflineTests] TEST_OFFLINE_POST_PURCHASE_EXT points at ${postPurchaseExtensionPath} but the file doesn't exist. ` +
736
740
  `Build the extension first — the bundled .js must exist on disk before the test run.`,
737
741
  );
738
742
  }
@@ -778,7 +782,7 @@ async function main(): Promise<void> {
778
782
  startOpts,
779
783
  );
780
784
  console.log(
781
- `[runOfflineFull] mocks ready:\n` +
785
+ `[runOfflineTests] mocks ready:\n` +
782
786
  ` shell: ${adminShell.baseUrl}\n` +
783
787
  ` admin GQL: ${adminApi.baseUrl}\n` +
784
788
  ` storefront GQL: ${storefrontApi.baseUrl}\n` +
@@ -796,9 +800,9 @@ async function main(): Promise<void> {
796
800
  for (const p of probes) {
797
801
  try {
798
802
  const r = await fetch(p.url, p.init);
799
- console.log(`[runOfflineFull] probe ${p.name}: ${r.status}`);
803
+ console.log(`[runOfflineTests] probe ${p.name}: ${r.status}`);
800
804
  } catch (e) {
801
- console.log(`[runOfflineFull] probe ${p.name}: FAILED ${(e as Error).message}`);
805
+ console.log(`[runOfflineTests] probe ${p.name}: FAILED ${(e as Error).message}`);
802
806
  }
803
807
  }
804
808
 
@@ -831,6 +835,7 @@ async function main(): Promise<void> {
831
835
  adminApi: adminApi.baseUrl,
832
836
  storefrontApi: storefrontApi.baseUrl,
833
837
  adminShell: adminShell.baseUrl,
838
+ partnerApi: partnerApi.baseUrl,
834
839
  },
835
840
  shopDomain: DEFAULT_SHOP_DOMAIN,
836
841
  // Previously bound dual-stack `::` so /etc/hosts entries for
@@ -842,7 +847,7 @@ async function main(): Promise<void> {
842
847
  hostname: '0.0.0.0',
843
848
  port: 443,
844
849
  });
845
- console.log(`[runOfflineFull] edge proxy: ${edge.baseUrl}`);
850
+ console.log(`[runOfflineTests] edge proxy: ${edge.baseUrl}`);
846
851
 
847
852
  // ── Neon-local WS proxy (zero-app-touch) ──────────────────
848
853
  // Bridge the app's @neondatabase/serverless driver to THIS run's local
@@ -851,7 +856,7 @@ async function main(): Promise<void> {
851
856
  // the proxy reads the driver's `?address=` and forwards to Postgres.
852
857
  const neonProxy = await startNeonWsProxy({ targetHost: PG_HOST, targetPort: Number(PG_PORT) });
853
858
  console.log(
854
- `[runOfflineFull] neon ws-proxy: 127.0.0.1:${neonProxy.port} → Postgres ${PG_HOST}:${PG_PORT}`,
859
+ `[runOfflineTests] neon ws-proxy: 127.0.0.1:${neonProxy.port} → Postgres ${PG_HOST}:${PG_PORT}`,
855
860
  );
856
861
 
857
862
  // ── Backend env ───────────────────────────────────────────
@@ -951,7 +956,7 @@ async function main(): Promise<void> {
951
956
  : {}),
952
957
  };
953
958
 
954
- console.log('[runOfflineFull] prisma migrate deploy…');
959
+ console.log('[runOfflineTests] prisma migrate deploy…');
955
960
  // Strip the MSW loader from NODE_OPTIONS for prisma's invocation.
956
961
  // MSW shouldn't intercept Postgres connections (it's HTTP-only),
957
962
  // but loading it from inside `npx prisma` triggers a silent
@@ -974,19 +979,19 @@ async function main(): Promise<void> {
974
979
  // a no-op for apps that don't need it.
975
980
  const extraSchema = resolve(process.cwd(), 'tests/offline/offline-extra-schema.sql');
976
981
  if (existsSync(extraSchema)) {
977
- console.log('[runOfflineFull] applying extra schema (tests/offline/offline-extra-schema.sql)…');
982
+ console.log('[runOfflineTests] applying extra schema (tests/offline/offline-extra-schema.sql)…');
978
983
  execSync(
979
984
  `psql -h ${PG_HOST} -p ${PG_PORT} -d ${dbName} -v ON_ERROR_STOP=1 -f ${JSON.stringify(extraSchema)}`,
980
985
  { stdio: 'inherit', env: prismaEnv },
981
986
  );
982
987
  }
983
988
 
984
- console.log('[runOfflineFull] seeding session row…');
989
+ console.log('[runOfflineTests] seeding session row…');
985
990
  try {
986
991
  seedSession(prismaEnv as Record<string, string>, DEFAULT_SHOP_DOMAIN);
987
992
  } catch (err) {
988
993
  console.warn(
989
- `[runOfflineFull] session seed failed (continuing): ${(err as Error).message}`,
994
+ `[runOfflineTests] session seed failed (continuing): ${(err as Error).message}`,
990
995
  );
991
996
  }
992
997
 
@@ -1044,7 +1049,7 @@ async function main(): Promise<void> {
1044
1049
  * the conventional exit code (`128 + signal`).
1045
1050
  */
1046
1051
  const onSignal = (sig: 'SIGINT' | 'SIGTERM'): void => {
1047
- console.log(`\n[runOfflineFull] received ${sig}, tearing down…`);
1052
+ console.log(`\n[runOfflineTests] received ${sig}, tearing down…`);
1048
1053
  cleanup();
1049
1054
  // 128 + signal number is the POSIX convention.
1050
1055
  process.exit(sig === 'SIGINT' ? 130 : 143);
@@ -1082,15 +1087,15 @@ async function main(): Promise<void> {
1082
1087
  const skipBuildBecauseCache = cachedHash === currentBuildHash;
1083
1088
 
1084
1089
  if (skipBuildBecauseExplicit) {
1085
- console.log('[runOfflineFull] skipping build (TEST_OFFLINE_SKIP_BUILD=true)');
1090
+ console.log('[runOfflineTests] skipping build (TEST_OFFLINE_SKIP_BUILD=true)');
1086
1091
  } else if (skipBuildBecauseCache) {
1087
1092
  console.log(
1088
- `[runOfflineFull] build cache HIT (${currentBuildHash.slice(0, 12)}) — skipping npm run build. ` +
1093
+ `[runOfflineTests] build cache HIT (${currentBuildHash.slice(0, 12)}) — skipping npm run build. ` +
1089
1094
  `Force with TEST_OFFLINE_FORCE_BUILD=true.`,
1090
1095
  );
1091
1096
  } else {
1092
1097
  console.log(
1093
- `[runOfflineFull] build cache MISS — running npm run build. ` +
1098
+ `[runOfflineTests] build cache MISS — running npm run build. ` +
1094
1099
  `(${cachedHash ? `was ${cachedHash.slice(0, 12)}, now ` : 'no cache, '}${currentBuildHash.slice(0, 12)})`,
1095
1100
  );
1096
1101
  execSync('npm run build', {
@@ -1101,7 +1106,7 @@ async function main(): Promise<void> {
1101
1106
  // crashed build leaves stale `build/` we shouldn't endorse.
1102
1107
  writeFileSync(buildCachePath, currentBuildHash);
1103
1108
  }
1104
- console.log('[runOfflineFull] booting Remix prod server (remix-serve)…');
1109
+ console.log('[runOfflineTests] booting Remix prod server (remix-serve)…');
1105
1110
  // Resolve remix-serve's CLI path directly — `npx remix-serve`
1106
1111
  // on some systems alters NODE_OPTIONS (it spawns a child via
1107
1112
  // npm-cli with PATH manipulation), which silently drops our
@@ -1140,9 +1145,9 @@ async function main(): Promise<void> {
1140
1145
  env: backendRuntimeEnv,
1141
1146
  },
1142
1147
  );
1143
- console.log(`[runOfflineFull] waiting for backend at ${backendUrl}…`);
1148
+ console.log(`[runOfflineTests] waiting for backend at ${backendUrl}…`);
1144
1149
  await waitForReady(backendUrl, READY_TIMEOUT_MS);
1145
- console.log('[runOfflineFull] backend ready.');
1150
+ console.log('[runOfflineTests] backend ready.');
1146
1151
 
1147
1152
  // ── Pre-warm Remix routes ──────────────────────────────────
1148
1153
  // remix-serve lazy-compiles each route's loader/action module on
@@ -1162,7 +1167,7 @@ async function main(): Promise<void> {
1162
1167
  //
1163
1168
  // Failures are non-fatal — the next real test request will
1164
1169
  // surface a clearer error than "warm-up failed".
1165
- console.log('[runOfflineFull] pre-warming hot Remix routes…');
1170
+ console.log('[runOfflineTests] pre-warming hot Remix routes…');
1166
1171
  const warmupToken = await mintIdToken({
1167
1172
  shop: DEFAULT_SHOP_DOMAIN,
1168
1173
  clientId: MOCK_CLIENT_ID,
@@ -1197,7 +1202,7 @@ async function main(): Promise<void> {
1197
1202
  authorization: `Bearer ${warmupToken}`,
1198
1203
  // shopify-app-remix's `isbot` check returns 410 for
1199
1204
  // Node's default User-Agent. Match the same UA the
1200
- // browser context uses in tests (see offlineFullStack
1205
+ // browser context uses in tests (see offlineStack
1201
1206
  // fixture) so the request reaches the loader.
1202
1207
  'user-agent':
1203
1208
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ' +
@@ -1218,7 +1223,7 @@ async function main(): Promise<void> {
1218
1223
  }),
1219
1224
  );
1220
1225
  console.log(
1221
- `[runOfflineFull] route pre-warm done (${Date.now() - t0}ms):`,
1226
+ `[runOfflineTests] route pre-warm done (${Date.now() - t0}ms):`,
1222
1227
  );
1223
1228
  for (const r of warmupResults) {
1224
1229
  const status =
@@ -1246,7 +1251,7 @@ async function main(): Promise<void> {
1246
1251
  const adminUrl =
1247
1252
  process.env['TEST_OFFLINE_EXPLORE_URL'] ??
1248
1253
  `https://admin.shopify.com/store/test/apps/${DEFAULT_APP_HANDLE}/app`;
1249
- console.log('[runOfflineFull] explore mode — launching Chrome at:');
1254
+ console.log('[runOfflineTests] explore mode — launching Chrome at:');
1250
1255
  console.log(` ${adminUrl}`);
1251
1256
  console.log('');
1252
1257
  console.log(' Useful URLs you can navigate to (all routed via the edge proxy):');
@@ -1356,7 +1361,7 @@ async function main(): Promise<void> {
1356
1361
  await sfPage.goto(storefrontUrl, { waitUntil: 'domcontentloaded' });
1357
1362
  } catch (err) {
1358
1363
  console.error(
1359
- `[runOfflineFull] explore: storefront tab failed to load (${(err as Error).message}) — continuing`,
1364
+ `[runOfflineTests] explore: storefront tab failed to load (${(err as Error).message}) — continuing`,
1360
1365
  );
1361
1366
  }
1362
1367
  // Leave the admin tab focused as the active one.
@@ -1381,7 +1386,7 @@ async function main(): Promise<void> {
1381
1386
  }
1382
1387
 
1383
1388
  // ── Playwright ─────────────────────────────────────────
1384
- console.log('[runOfflineFull] running Playwright…');
1389
+ console.log('[runOfflineTests] running Playwright…');
1385
1390
  // Headed-under-Xvfb is the DEFAULT for offline runs (production-
1386
1391
  // faithful, ~4% slower than headless). Opt OUT with
1387
1392
  // TEST_OFFLINE_HEADLESS=true. Full-screen recording (TEST_OFFLINE_VIDEO)
@@ -1406,7 +1411,7 @@ async function main(): Promise<void> {
1406
1411
  }
1407
1412
  if (!hasFfmpeg) {
1408
1413
  console.error(
1409
- '[runOfflineFull] TEST_OFFLINE_VIDEO set but `ffmpeg` is missing from the ' +
1414
+ '[runOfflineTests] TEST_OFFLINE_VIDEO set but `ffmpeg` is missing from the ' +
1410
1415
  'VM image — rebuild it (it predates video support): ' +
1411
1416
  '`container image rm essential-apps/shopify-test-vm:latest` then re-run. ' +
1412
1417
  'Continuing headed WITHOUT recording.',
@@ -1433,7 +1438,7 @@ async function main(): Promise<void> {
1433
1438
  headedReady = existsSync('/tmp/.X11-unix/X99');
1434
1439
  if (!headedReady) {
1435
1440
  console.error(
1436
- '[runOfflineFull] WARNING: Xvfb :99 did not start — falling back to headless',
1441
+ '[runOfflineTests] WARNING: Xvfb :99 did not start — falling back to headless',
1437
1442
  );
1438
1443
  videoOn = false;
1439
1444
  headedOn = false;
@@ -1441,7 +1446,7 @@ async function main(): Promise<void> {
1441
1446
  process.env['TEST_OFFLINE_HEADED'] = 'false';
1442
1447
  } else {
1443
1448
  console.error(
1444
- `[runOfflineFull] headed under Xvfb :99 (${videoOn ? 'recording' : 'no recording'})`,
1449
+ `[runOfflineTests] headed under Xvfb :99 (${videoOn ? 'recording' : 'no recording'})`,
1445
1450
  );
1446
1451
  }
1447
1452
  }
@@ -1487,19 +1492,13 @@ async function main(): Promise<void> {
1487
1492
  // (backendEnv sets it too). Without it the helper's credit comes back
1488
1493
  // test:false and the assertion fails.
1489
1494
  BILLING_IS_TEST: process.env['BILLING_IS_TEST'] ?? 'true',
1490
- // Point the app's Partner API client at the in-VM partner mock.
1491
- //
1492
- // The app fetches `https://partners.shopify.com/<org>/api/<ver>/
1493
- // graphql.json`; the backend reaches the mock because its preloaded
1494
- // MSW rewrites that origin. We deliberately do NOT preload MSW here:
1495
- // MSW globally patches Node networking and corrupts Playwright's
1496
- // `page.request` (APIRequestContext) — it broke unrelated specs that
1497
- // hit `page.request.post(...)`. Instead the app's PartnerApiClient
1498
- // honours this env override for the base origin (a prod-safe test
1499
- // seam; unset in prod → real URL). The mock serves the identical
1500
- // `/:org/api/:ver/graphql.json` path, so only the origin changes —
1501
- // the real client / GraphQL / response-parsing all still run.
1502
- SHOPIFY_PARTNER_API_BASE_URL: partnerApi.baseUrl,
1495
+ // NB: the app's Partner API client fetches its hard-coded
1496
+ // https://partners.shopify.com endpoint with NO override. That host
1497
+ // is routed to the in-VM partner mock via /etc/hosts → the edge
1498
+ // proxy (see ensureOfflineSetup + edgeProxy's partners.shopify.com
1499
+ // case), so the issue-credit helper's Node fetch reaches the mock
1500
+ // with no app-side change and no MSW preload (which would corrupt
1501
+ // Playwright's page.request).
1503
1502
  };
1504
1503
  // Use async spawn (NOT execSync) — execSync blocks the
1505
1504
  // orchestrator's event loop, so the in-process mock servers
@@ -1526,7 +1525,7 @@ async function main(): Promise<void> {
1526
1525
  // a file path, `--repeat-each`, etc. The docker runner already
1527
1526
  // forwards user args verbatim into this script's argv; we just
1528
1527
  // pass them through to playwright so single-test iteration is
1529
- // possible (`npm run test:offline-full -- --grep "PDP form"`).
1528
+ // possible (`npm run test:offline -- --grep "PDP form"`).
1530
1529
  // Skip our own known flags (currently none after the launcher
1531
1530
  // strips them — process.argv[0] is `node`, argv[1] is this
1532
1531
  // script).
@@ -1549,9 +1548,9 @@ async function main(): Promise<void> {
1549
1548
  }
1550
1549
  exitCode = 0;
1551
1550
  } catch (err) {
1552
- console.error(`[runOfflineFull] failed: ${(err as Error).message}`);
1551
+ console.error(`[runOfflineTests] failed: ${(err as Error).message}`);
1553
1552
  } finally {
1554
- console.log('[runOfflineFull] tearing down…');
1553
+ console.log('[runOfflineTests] tearing down…');
1555
1554
  // `cleanup()` kills the backend AND drops the DB (with
1556
1555
  // preserveDb honored). It's the same function the signal
1557
1556
  // handlers call, so the teardown path is identical whether
@@ -1571,7 +1570,7 @@ async function main(): Promise<void> {
1571
1570
  ]);
1572
1571
 
1573
1572
  if (preserveDb) {
1574
- console.log(`[runOfflineFull] DB preserved: ${dbName}`);
1573
+ console.log(`[runOfflineTests] DB preserved: ${dbName}`);
1575
1574
  }
1576
1575
  }
1577
1576
 
@@ -1600,28 +1599,28 @@ function loadExtensions(): ExtensionConfig[] {
1600
1599
  parsed = JSON.parse(raw);
1601
1600
  } catch (err) {
1602
1601
  throw new Error(
1603
- `[runOfflineFull] TEST_OFFLINE_EXTENSIONS_JSON is not valid JSON: ${(err as Error).message}`,
1602
+ `[runOfflineTests] TEST_OFFLINE_EXTENSIONS_JSON is not valid JSON: ${(err as Error).message}`,
1604
1603
  );
1605
1604
  }
1606
1605
  if (!Array.isArray(parsed)) {
1607
1606
  throw new Error(
1608
- `[runOfflineFull] TEST_OFFLINE_EXTENSIONS_JSON must be a JSON array; got ${typeof parsed}`,
1607
+ `[runOfflineTests] TEST_OFFLINE_EXTENSIONS_JSON must be a JSON array; got ${typeof parsed}`,
1609
1608
  );
1610
1609
  }
1611
1610
  const out: ExtensionConfig[] = [];
1612
1611
  for (const entry of parsed) {
1613
1612
  if (typeof entry !== 'object' || entry === null) {
1614
- throw new Error(`[runOfflineFull] extension entry must be an object: ${JSON.stringify(entry)}`);
1613
+ throw new Error(`[runOfflineTests] extension entry must be an object: ${JSON.stringify(entry)}`);
1615
1614
  }
1616
1615
  const e = entry as { name?: unknown; rootDir?: unknown; enabled?: unknown };
1617
1616
  if (typeof e.name !== 'string' || typeof e.rootDir !== 'string') {
1618
1617
  throw new Error(
1619
- `[runOfflineFull] each extension needs { name: string, rootDir: string }; got ${JSON.stringify(entry)}`,
1618
+ `[runOfflineTests] each extension needs { name: string, rootDir: string }; got ${JSON.stringify(entry)}`,
1620
1619
  );
1621
1620
  }
1622
1621
  const rootDir = resolve(process.cwd(), e.rootDir);
1623
1622
  if (!existsSync(rootDir)) {
1624
- throw new Error(`[runOfflineFull] extension rootDir does not exist: ${rootDir}`);
1623
+ throw new Error(`[runOfflineTests] extension rootDir does not exist: ${rootDir}`);
1625
1624
  }
1626
1625
  out.push({
1627
1626
  name: e.name,
@@ -1631,7 +1630,7 @@ function loadExtensions(): ExtensionConfig[] {
1631
1630
  }
1632
1631
  if (out.length > 0) {
1633
1632
  console.log(
1634
- `[runOfflineFull] loaded ${out.length} extension(s): ${out.map((e) => `${e.name}=${e.rootDir}`).join(', ')}`,
1633
+ `[runOfflineTests] loaded ${out.length} extension(s): ${out.map((e) => `${e.name}=${e.rootDir}`).join(', ')}`,
1635
1634
  );
1636
1635
  }
1637
1636
  return out;
@@ -1755,7 +1754,7 @@ function seedExploreProducts(state: ShopState): void {
1755
1754
  }
1756
1755
 
1757
1756
  async function runSupervisor(workers: number): Promise<number> {
1758
- console.log(`[runOfflineFull] supervisor: spawning ${workers} shard(s)…`);
1757
+ console.log(`[runOfflineTests] supervisor: spawning ${workers} shard(s)…`);
1759
1758
 
1760
1759
  // Pre-build: each child would otherwise duplicate the build (or
1761
1760
  // race on the cache). Build here once, then pass SKIP_BUILD=true
@@ -1769,9 +1768,9 @@ async function runSupervisor(workers: number): Promise<number> {
1769
1768
  ? readFileSync(buildCachePath, 'utf8').trim()
1770
1769
  : null;
1771
1770
  if (cachedHash === currentBuildHash) {
1772
- console.log(`[runOfflineFull] supervisor: build cache HIT (${currentBuildHash.slice(0, 12)})`);
1771
+ console.log(`[runOfflineTests] supervisor: build cache HIT (${currentBuildHash.slice(0, 12)})`);
1773
1772
  } else {
1774
- console.log(`[runOfflineFull] supervisor: build cache MISS — running npm run build…`);
1773
+ console.log(`[runOfflineTests] supervisor: build cache MISS — running npm run build…`);
1775
1774
  execSync('npm run build', {
1776
1775
  stdio: 'inherit',
1777
1776
  // `TEST_OFFLINE=true` switches the consuming app's
@@ -1807,7 +1806,7 @@ async function runSupervisor(workers: number): Promise<number> {
1807
1806
  children.push(
1808
1807
  new Promise<number>((resolveChild, rejectChild) => {
1809
1808
  // Re-exec ourselves through `node --import tsx` so TS still
1810
- // parses. argv[1] points at the user's `runOfflineFullTests.ts`
1809
+ // parses. argv[1] points at the user's `runOfflineTests.ts`
1811
1810
  // by the time we're invoked from the consuming app's
1812
1811
  // npm script.
1813
1812
  const proc = spawn(
@@ -1823,7 +1822,7 @@ async function runSupervisor(workers: number): Promise<number> {
1823
1822
  const exitCodes = await Promise.all(children);
1824
1823
  const worst = exitCodes.reduce((a, b) => Math.max(a, b), 0);
1825
1824
  console.log(
1826
- `[runOfflineFull] supervisor: shard exit codes [${exitCodes.join(', ')}]; overall=${worst}`,
1825
+ `[runOfflineTests] supervisor: shard exit codes [${exitCodes.join(', ')}]; overall=${worst}`,
1827
1826
  );
1828
1827
  return worst;
1829
1828
  }
@@ -164,7 +164,7 @@ async function main(): Promise<void> {
164
164
  // from .env.test only.
165
165
  const backendEnv: NodeJS.ProcessEnv = {
166
166
  PATH: process.env['PATH'] ?? '',
167
- // See runOfflineFullTests.ts for the long-form note — empty HOME
167
+ // See runOfflineTests.ts for the long-form note — empty HOME
168
168
  // makes npm write its cache to literal `~/.npm` in cwd.
169
169
  HOME: process.env['HOME'] || '/root',
170
170
  USER: process.env['USER'] ?? '',
@@ -10,7 +10,7 @@
10
10
  * ───────── ────────────
11
11
  * amd64 image (Rosetta) arm64 image (native)
12
12
  * ~10 s cold boot ~5 s cold bake / ~1 s warm restore
13
- * ~30 s/test wall-clock ~3× faster end-to-end on offline-full
13
+ * ~30 s/test wall-clock ~3× faster end-to-end on offline
14
14
  * Google Chrome amd64 in Xvfb Playwright bundled chromium in
15
15
  * Xvfb (patchright stealth still works)
16
16
  *
@@ -553,7 +553,7 @@ async function main(): Promise<void> {
553
553
  });
554
554
 
555
555
  // Stream + collect output. Hard 10-min cap by default, with a
556
- // 60 s no-output stall guard (mirrors the offline-full bench's
556
+ // 60 s no-output stall guard (mirrors the offline bench's
557
557
  // proven pattern for live processes).
558
558
  const maxMs = Number(process.env['TEST_ONLINE_VM_TIMEOUT_MS'] ?? 10 * 60 * 1000);
559
559
  // Stall guard: kill the test process if no output for N seconds.
@@ -5,7 +5,7 @@ import { loadConfigFromFile, mergeConfig, type ConfigEnv, type UserConfig } from
5
5
  * suite — `runTests.ts` boots the dev server with
6
6
  * `vite --config <this file>`. It is never referenced by an app's own
7
7
  * `vite dev` / `vite build`, so it cannot change any app's production
8
- * or local-dev behaviour. (Offline-full serves a production build via
8
+ * or local-dev behaviour. (Offline serves a production build via
9
9
  * remix-serve; conformance runs VM probes — neither uses Vite dev, so
10
10
  * this is online-only.)
11
11
  *
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
3
- //# sourceMappingURL=runOfflineFullTests.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runOfflineFullTests.d.ts","sourceRoot":"","sources":["../../src/scripts/runOfflineFullTests.ts"],"names":[],"mappings":""}