@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,257 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Probe runner — entry point for the online-store inspection framework.
|
|
4
|
+
*
|
|
5
|
+
* Invocation:
|
|
6
|
+
* node --import tsx \
|
|
7
|
+
* node_modules/@essential-apps/shopify-test-runner/src/probes/runProbe.ts \
|
|
8
|
+
* <probe-name> [--key=value …]
|
|
9
|
+
*
|
|
10
|
+
* What it does:
|
|
11
|
+
* 1. Loads the consuming app's `.env.test` so probes can read
|
|
12
|
+
* env (e.g. STOREFRONT_PASSWORD).
|
|
13
|
+
* 2. Picks an installed store from `tests/test-online/.stores.json`.
|
|
14
|
+
* 3. Launches Playwright chromium via patchright with the
|
|
15
|
+
* saved Partner storage state — same recipe online tests use.
|
|
16
|
+
* 4. Hands control to the probe.
|
|
17
|
+
*
|
|
18
|
+
* Adding a probe: drop a file in `packages/runner/src/probes/<name>.ts`
|
|
19
|
+
* default-exporting a {@link Probe}, and add a row to `PROBES` below.
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
22
|
+
import { resolve, dirname } from 'node:path';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
import { type BrowserContext } from '@playwright/test';
|
|
25
|
+
import {
|
|
26
|
+
readRegistry,
|
|
27
|
+
storageStatePath,
|
|
28
|
+
type Store,
|
|
29
|
+
} from '@essential-apps/shopify-test-core';
|
|
30
|
+
import type { Probe, ProbeContext } from './types.js';
|
|
31
|
+
import fontsProbe from './fonts.js';
|
|
32
|
+
import mirrorProbe from './mirror.js';
|
|
33
|
+
import { launchStealthBrowser } from '../lib/stealthLaunch.js';
|
|
34
|
+
|
|
35
|
+
// ── Registry of available probes ──────────────────────────────
|
|
36
|
+
// Order matters only for `--help` listing.
|
|
37
|
+
const PROBES: Record<string, Probe> = {
|
|
38
|
+
[fontsProbe.name]: fontsProbe,
|
|
39
|
+
[mirrorProbe.name]: mirrorProbe,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// ── CLI arg parsing ───────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
interface ParsedArgs {
|
|
45
|
+
name: string;
|
|
46
|
+
args: Record<string, string>;
|
|
47
|
+
help: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseArgs(argv: string[]): ParsedArgs {
|
|
51
|
+
const [name, ...rest] = argv;
|
|
52
|
+
const args: Record<string, string> = {};
|
|
53
|
+
let help = false;
|
|
54
|
+
for (const a of rest) {
|
|
55
|
+
if (a === '--help' || a === '-h') {
|
|
56
|
+
help = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const m = a.match(/^--([^=]+)=(.*)$/);
|
|
60
|
+
if (m) {
|
|
61
|
+
args[m[1]!] = m[2]!;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
// Bare flag: --foo (no value) → true
|
|
65
|
+
const bare = a.match(/^--(.+)$/);
|
|
66
|
+
if (bare) {
|
|
67
|
+
args[bare[1]!] = '';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { name: name ?? '', args, help };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function printUsage(): void {
|
|
74
|
+
console.error('Usage: runProbe <name> [--key=value …]');
|
|
75
|
+
console.error('');
|
|
76
|
+
console.error('Available probes:');
|
|
77
|
+
for (const p of Object.values(PROBES)) {
|
|
78
|
+
console.error(` ${p.name.padEnd(14)} ${p.description}`);
|
|
79
|
+
}
|
|
80
|
+
console.error('');
|
|
81
|
+
console.error('Global flags:');
|
|
82
|
+
console.error(' --help Show this help');
|
|
83
|
+
console.error(' --store=<slug> Use a specific store from the pool (default: any available)');
|
|
84
|
+
console.error('');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Workspace path discovery ──────────────────────────────────
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Locate the workspace root. When this script runs from a packed
|
|
91
|
+
* tarball inside a consumer's node_modules, the workspace is the
|
|
92
|
+
* shopify-test repo's root — the package nearest us with
|
|
93
|
+
* `name === "@essential-apps/shopify-test"`. When running from the
|
|
94
|
+
* repo itself, that's the repo root.
|
|
95
|
+
*
|
|
96
|
+
* We walk up from this file looking for a `package.json` whose
|
|
97
|
+
* `name` matches, falling back to the nearest directory containing
|
|
98
|
+
* a `packages/` folder (matches the monorepo layout).
|
|
99
|
+
*/
|
|
100
|
+
function findWorkspaceRoot(): string {
|
|
101
|
+
// When invoked from a consumer, the runner's source lives under
|
|
102
|
+
// `node_modules/@essential-apps/shopify-test-runner/src/`. That
|
|
103
|
+
// path doesn't help us reach the workspace's themes/, etc.
|
|
104
|
+
// Look upward from this file for the workspace root marker.
|
|
105
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
106
|
+
for (let i = 0; i < 10; i++) {
|
|
107
|
+
const pj = resolve(dir, 'package.json');
|
|
108
|
+
if (existsSync(pj)) {
|
|
109
|
+
try {
|
|
110
|
+
const pkg = JSON.parse(readFileSync(pj, 'utf8')) as { name?: string };
|
|
111
|
+
if (pkg.name === '@essential-apps/shopify-test') return dir;
|
|
112
|
+
} catch {
|
|
113
|
+
/* fall through */
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const parent = resolve(dir, '..');
|
|
117
|
+
if (parent === dir) break;
|
|
118
|
+
dir = parent;
|
|
119
|
+
}
|
|
120
|
+
// Fallback: assume cwd is the consumer app — outputs go to its
|
|
121
|
+
// tests/test-online/probe-output/ if we can't find a workspace.
|
|
122
|
+
return process.cwd();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Store selection ───────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
function pickStore(slug?: string): Store {
|
|
128
|
+
const { stores } = readRegistry();
|
|
129
|
+
if (slug) {
|
|
130
|
+
const s = stores.find((x) => x.slug === slug);
|
|
131
|
+
if (!s) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`Store with slug "${slug}" not found in registry. ` +
|
|
134
|
+
`Available: ${stores.map((x) => x.slug).join(', ')}`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return s;
|
|
138
|
+
}
|
|
139
|
+
const candidate =
|
|
140
|
+
stores.find((s) => s.appInstalled && s.status === 'available') ??
|
|
141
|
+
stores.find((s) => s.appInstalled) ??
|
|
142
|
+
stores[0];
|
|
143
|
+
if (!candidate) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
'No stores in registry. Run `npm run test:online:add` + `npm run test:online:install` first.',
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
return candidate;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Synthesize a Store record from a bare URL. Used by `--url=<full>`
|
|
153
|
+
* mode so probes can run against arbitrary public Shopify storefronts
|
|
154
|
+
* (e.g. `theme-dawn-demo.myshopify.com` for canonical Dawn font
|
|
155
|
+
* mirroring) without needing a Partner-auth registry entry.
|
|
156
|
+
*/
|
|
157
|
+
function storeFromUrl(rawUrl: string): Store {
|
|
158
|
+
const u = new URL(rawUrl);
|
|
159
|
+
const shop = u.hostname;
|
|
160
|
+
const slug = shop.replace(/\.myshopify\.com$/, '');
|
|
161
|
+
return {
|
|
162
|
+
shop,
|
|
163
|
+
slug,
|
|
164
|
+
plan: 'PUBLIC',
|
|
165
|
+
appInstalled: false,
|
|
166
|
+
status: 'available',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Browser bootstrap ─────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
async function launchBrowser(opts: { needAuth: boolean }): Promise<BrowserContext> {
|
|
173
|
+
// Reuse the same chrome+patchright recipe as the online-test store
|
|
174
|
+
// pool: real Google Chrome via patchright. For probes against
|
|
175
|
+
// Partner-owned stores we layer in the saved Partner storage
|
|
176
|
+
// state so password gates / admin redirects work; for public
|
|
177
|
+
// storefronts (--url mode) we skip that — no auth needed.
|
|
178
|
+
const browser = await launchStealthBrowser({ headless: true });
|
|
179
|
+
const storageState =
|
|
180
|
+
opts.needAuth && existsSync(storageStatePath)
|
|
181
|
+
? JSON.parse(readFileSync(storageStatePath, 'utf8'))
|
|
182
|
+
: undefined;
|
|
183
|
+
if (opts.needAuth && !storageState) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`Storage state not found at ${storageStatePath}. ` +
|
|
186
|
+
`Run online tests at least once to capture Partner auth cookies, ` +
|
|
187
|
+
`or pass --url=<public-store-url> to skip auth.`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
const context = await browser.newContext({
|
|
191
|
+
viewport: { width: 1400, height: 900 },
|
|
192
|
+
ignoreHTTPSErrors: true,
|
|
193
|
+
...(storageState ? { storageState } : {}),
|
|
194
|
+
});
|
|
195
|
+
return context;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── Main ──────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
async function main(): Promise<void> {
|
|
201
|
+
const parsed = parseArgs(process.argv.slice(2));
|
|
202
|
+
if (parsed.help || !parsed.name) {
|
|
203
|
+
printUsage();
|
|
204
|
+
process.exit(parsed.name ? 0 : 1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const probe = PROBES[parsed.name];
|
|
208
|
+
if (!probe) {
|
|
209
|
+
console.error(`Unknown probe: "${parsed.name}"\n`);
|
|
210
|
+
printUsage();
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const workspaceRoot = findWorkspaceRoot();
|
|
215
|
+
// --url=<full> mode: synthesize a Store record from the URL and
|
|
216
|
+
// skip auth. For probing arbitrary public storefronts (e.g.
|
|
217
|
+
// theme-dawn-demo.myshopify.com for canonical Dawn font mirroring).
|
|
218
|
+
const urlMode = parsed.args['url'];
|
|
219
|
+
const store = urlMode ? storeFromUrl(urlMode) : pickStore(parsed.args['store']);
|
|
220
|
+
const outputDir = resolve(
|
|
221
|
+
workspaceRoot,
|
|
222
|
+
probe.outputDir.replace(/\$\{name\}/g, probe.name),
|
|
223
|
+
);
|
|
224
|
+
mkdirSync(outputDir, { recursive: true });
|
|
225
|
+
|
|
226
|
+
console.log(`[runProbe] probe: ${probe.name}`);
|
|
227
|
+
console.log(`[runProbe] store: ${store.shop} (${store.slug})${urlMode ? ' [public --url mode]' : ''}`);
|
|
228
|
+
console.log(`[runProbe] output: ${outputDir}`);
|
|
229
|
+
|
|
230
|
+
const browserContext = await launchBrowser({ needAuth: !urlMode });
|
|
231
|
+
const page = await browserContext.newPage();
|
|
232
|
+
|
|
233
|
+
const ctx: ProbeContext = {
|
|
234
|
+
store,
|
|
235
|
+
browserContext,
|
|
236
|
+
page,
|
|
237
|
+
outputDir,
|
|
238
|
+
args: parsed.args,
|
|
239
|
+
workspaceRoot,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
await probe.run(ctx);
|
|
244
|
+
console.log(`[runProbe] ✓ ${probe.name} complete`);
|
|
245
|
+
} catch (err) {
|
|
246
|
+
console.error(`[runProbe] ✗ ${probe.name} failed:`, err);
|
|
247
|
+
process.exitCode = 1;
|
|
248
|
+
} finally {
|
|
249
|
+
await browserContext.close().catch(() => {});
|
|
250
|
+
await browserContext.browser()?.close().catch(() => {});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
main().catch((err) => {
|
|
255
|
+
console.error(err);
|
|
256
|
+
process.exit(1);
|
|
257
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Probe framework — a reusable harness for asking real Shopify
|
|
3
|
+
* "what does this actually look like?" against the online test store
|
|
4
|
+
* pool.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: every offline mock has a long tail of "is our
|
|
7
|
+
* shape right?" questions. Examples we've already hit:
|
|
8
|
+
* - what URLs does Shopify's `font_face` Liquid filter emit?
|
|
9
|
+
* - what does the real `themes/{id}/assets.json` response look like?
|
|
10
|
+
* - what HTTP headers does fonts.shopifycdn.com set on a .woff2?
|
|
11
|
+
* - what's the exact `settings_data.json` shape when an app embed
|
|
12
|
+
* is activated?
|
|
13
|
+
*
|
|
14
|
+
* Each probe is a small Node script that drives a Playwright browser
|
|
15
|
+
* against a real online store (picked from `tests/test-online/.stores.json`),
|
|
16
|
+
* captures structured fixture data, and writes it somewhere the
|
|
17
|
+
* mocks can read at runtime.
|
|
18
|
+
*
|
|
19
|
+
* Adding a probe: create `packages/runner/src/probes/<name>.ts`,
|
|
20
|
+
* default-export an object conforming to {@link Probe}, and add a
|
|
21
|
+
* row to the registry in `runProbe.ts`.
|
|
22
|
+
*
|
|
23
|
+
* Invoke from the consuming app's repo:
|
|
24
|
+
* node --import tsx \
|
|
25
|
+
* node_modules/@essential-apps/shopify-test-runner/src/probes/runProbe.ts \
|
|
26
|
+
* <probe-name> [--key=value …]
|
|
27
|
+
*/
|
|
28
|
+
import type { BrowserContext, Page } from '@playwright/test';
|
|
29
|
+
import type { Store } from '@essential-apps/shopify-test-core';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Per-probe execution context. The runner sets up the browser +
|
|
33
|
+
* store before invoking the probe; the probe focuses on what it
|
|
34
|
+
* captures, not how to get a browser.
|
|
35
|
+
*/
|
|
36
|
+
export interface ProbeContext {
|
|
37
|
+
/** The online store this probe runs against. */
|
|
38
|
+
store: Store;
|
|
39
|
+
/** Logged-in Playwright BrowserContext (storage state pre-loaded). */
|
|
40
|
+
browserContext: BrowserContext;
|
|
41
|
+
/** Convenience: a fresh page in the context. Probes can open more. */
|
|
42
|
+
page: Page;
|
|
43
|
+
/**
|
|
44
|
+
* Absolute path where probe artifacts go. Probes write fixture
|
|
45
|
+
* files (JSON, binary, etc.) under here so the mock packages
|
|
46
|
+
* can read them later. Conventionally the workspace path
|
|
47
|
+
* `packages/<owner-package>/fixtures/<probe-name>/`.
|
|
48
|
+
*/
|
|
49
|
+
outputDir: string;
|
|
50
|
+
/** Parsed --key=value CLI args. */
|
|
51
|
+
args: Record<string, string>;
|
|
52
|
+
/** Workspace root absolute path (the shopify-test repo). */
|
|
53
|
+
workspaceRoot: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface Probe {
|
|
57
|
+
/** Probe name — what you type after `runProbe`. */
|
|
58
|
+
name: string;
|
|
59
|
+
/** One-line description shown in `runProbe --help`. */
|
|
60
|
+
description: string;
|
|
61
|
+
/**
|
|
62
|
+
* Where outputs should go, relative to workspaceRoot. The harness
|
|
63
|
+
* creates this dir and passes its absolute path as
|
|
64
|
+
* `ctx.outputDir`. Use `${name}` etc. — substituted at runtime.
|
|
65
|
+
*/
|
|
66
|
+
outputDir: string;
|
|
67
|
+
/**
|
|
68
|
+
* Main entry point. Throws on failure; logs progress to stdout.
|
|
69
|
+
* Should be idempotent — re-running a probe should refresh
|
|
70
|
+
* outputs cleanly.
|
|
71
|
+
*/
|
|
72
|
+
run(ctx: ProbeContext): Promise<void>;
|
|
73
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import {
|
|
4
|
+
loadEnv,
|
|
5
|
+
printEnvSummary,
|
|
6
|
+
appendStore,
|
|
7
|
+
readRegistry,
|
|
8
|
+
} from '@essential-apps/shopify-test-core';
|
|
9
|
+
|
|
10
|
+
async function main(): Promise<void> {
|
|
11
|
+
const { values } = parseArgs({
|
|
12
|
+
options: {
|
|
13
|
+
shop: { type: 'string' },
|
|
14
|
+
plan: { type: 'string' },
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const shop = values.shop?.trim();
|
|
19
|
+
if (!shop) {
|
|
20
|
+
console.error(
|
|
21
|
+
'Usage: npm run test:online:add -- --shop online-1.myshopify.com [--plan UNLIMITED_APP_DEVELOPMENT]',
|
|
22
|
+
);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
if (!/^[a-z0-9-]+\.myshopify\.com$/.test(shop)) {
|
|
26
|
+
console.error(`Invalid shop format. Expected "<slug>.myshopify.com", got "${shop}".`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const env = loadEnv();
|
|
31
|
+
printEnvSummary(env);
|
|
32
|
+
|
|
33
|
+
const reg = readRegistry();
|
|
34
|
+
if (reg.stores.some((s) => s.shop === shop)) {
|
|
35
|
+
console.error(`Already in registry: ${shop}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const slug = shop.replace('.myshopify.com', '');
|
|
40
|
+
appendStore({
|
|
41
|
+
shop,
|
|
42
|
+
slug,
|
|
43
|
+
plan: values.plan ?? env.plan,
|
|
44
|
+
createdAt: new Date().toISOString(),
|
|
45
|
+
appInstalled: false,
|
|
46
|
+
status: 'available',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
console.log(`✓ Added ${shop} to pool.`);
|
|
50
|
+
console.log('');
|
|
51
|
+
console.log('Next: install the dev app on this store.');
|
|
52
|
+
console.log(` Make sure your dev server is running, then:`);
|
|
53
|
+
console.log(` npm run test:online:install -- --shop ${shop}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
main().catch((err) => {
|
|
57
|
+
console.error(err);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Build the canonical `shopify-test` image from the Dockerfile
|
|
4
|
+
* bundled in this package. Run from the shopify-test workspace
|
|
5
|
+
* root:
|
|
6
|
+
*
|
|
7
|
+
* npm run docker:build
|
|
8
|
+
*
|
|
9
|
+
* Or directly:
|
|
10
|
+
*
|
|
11
|
+
* node --import tsx packages/runner/src/scripts/buildDockerImage.ts
|
|
12
|
+
*
|
|
13
|
+
* The image is amd64-only (Rosetta on Apple Silicon) — see
|
|
14
|
+
* docker/README.md for why.
|
|
15
|
+
*/
|
|
16
|
+
import { spawn } from 'node:child_process';
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
import { dirname, resolve } from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
|
|
21
|
+
// Mirrors the npm scope (`@essential-apps/shopify-test`) so anyone
|
|
22
|
+
// inspecting `container images` sees the same name they `npm install`.
|
|
23
|
+
const DEFAULT_TAG = 'essential-apps/shopify-test:latest';
|
|
24
|
+
|
|
25
|
+
function dockerContextDir(): string {
|
|
26
|
+
// This file lives at packages/runner/src/scripts/ (or dist/scripts/
|
|
27
|
+
// after build). Walk up + sideways to docker/.
|
|
28
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
// here = .../packages/runner/{src,dist}/scripts
|
|
30
|
+
const candidates = [
|
|
31
|
+
resolve(here, '../../docker'),
|
|
32
|
+
resolve(here, '../docker'),
|
|
33
|
+
];
|
|
34
|
+
for (const c of candidates) {
|
|
35
|
+
if (existsSync(resolve(c, 'Dockerfile'))) return c;
|
|
36
|
+
}
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Could not locate the Dockerfile under packages/runner/docker/. ` +
|
|
39
|
+
`Tried: ${candidates.join(', ')}`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function main(): Promise<void> {
|
|
44
|
+
const tag = process.env['SHOPIFY_TEST_IMAGE_TAG'] ?? DEFAULT_TAG;
|
|
45
|
+
const context = dockerContextDir();
|
|
46
|
+
|
|
47
|
+
console.log('────────────────────────────────────────────────────────────');
|
|
48
|
+
console.log(' shopify-test docker image build');
|
|
49
|
+
console.log('────────────────────────────────────────────────────────────');
|
|
50
|
+
console.log(` Tag : ${tag}`);
|
|
51
|
+
console.log(` Context : ${context}`);
|
|
52
|
+
console.log(` Arch : amd64`);
|
|
53
|
+
console.log('────────────────────────────────────────────────────────────');
|
|
54
|
+
console.log('');
|
|
55
|
+
|
|
56
|
+
const args = ['build', '--arch', 'amd64', '--tag', tag, context];
|
|
57
|
+
const p = spawn('container', args, { stdio: 'inherit' });
|
|
58
|
+
p.on('exit', (code) => process.exit(code ?? 0));
|
|
59
|
+
process.on('SIGINT', () => p.kill('SIGINT'));
|
|
60
|
+
process.on('SIGTERM', () => p.kill('SIGTERM'));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
main().catch((err) => {
|
|
64
|
+
console.error(err);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Capture Shopify auth state into tests/test-online/.auth/storageState.json.
|
|
4
|
+
*
|
|
5
|
+
* The captured cookies are fingerprint-bound (UA, sec-ch-ua-platform,
|
|
6
|
+
* canvas/WebGL hashes, etc.), so this script MUST run in the same
|
|
7
|
+
* browser environment that tests use:
|
|
8
|
+
* - On host (macOS): Google Chrome via patchright.
|
|
9
|
+
* - In container: Google Chrome via patchright (same channel),
|
|
10
|
+
* under Xvfb. The runDockerAuth.ts wrapper exposes the Xvfb
|
|
11
|
+
* display over VNC so the user can interactively log in.
|
|
12
|
+
*
|
|
13
|
+
* If you re-capture on host but tests run in container (or vice-versa),
|
|
14
|
+
* Cloudflare's bot heuristics will see UA / fingerprint inconsistency
|
|
15
|
+
* and challenge or block the session.
|
|
16
|
+
*/
|
|
17
|
+
import { mkdirSync } from 'node:fs';
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
19
|
+
import { dirname } from 'node:path';
|
|
20
|
+
import { createInterface } from 'node:readline';
|
|
21
|
+
import type { BrowserContext, chromium as playwrightChromium } from '@playwright/test';
|
|
22
|
+
import { storageStatePath, assertInVm } from '@essential-apps/shopify-test-core';
|
|
23
|
+
|
|
24
|
+
// Auth capture must run INSIDE the VM (via runVmAuth, which sets
|
|
25
|
+
// TEST_IN_CONTAINER + drives this over VNC) — never on the host, or the
|
|
26
|
+
// captured cf_clearance/session is fingerprint-mismatched against the VM.
|
|
27
|
+
assertInVm('capture Shopify auth');
|
|
28
|
+
|
|
29
|
+
const require = createRequire(import.meta.url);
|
|
30
|
+
const chromium = (require('patchright') as { chromium: typeof playwrightChromium }).chromium;
|
|
31
|
+
|
|
32
|
+
// Test Partner account (throwaway; not prod). Shown in the terminal AND
|
|
33
|
+
// pinned to a bar at the top of the browser so it's readable while
|
|
34
|
+
// logging in over VNC.
|
|
35
|
+
const LOGIN_EMAIL = 'essential-apps-test-1@supercorp.ai';
|
|
36
|
+
const LOGIN_PASSWORD = 'essential-apps-test-1-password';
|
|
37
|
+
|
|
38
|
+
// A fixed top bar injected into every page (survives navigation) showing
|
|
39
|
+
// the login creds. `pointer-events:none` so it never blocks the form;
|
|
40
|
+
// re-pinned on an interval because Shopify's accounts pages re-render.
|
|
41
|
+
const credsBarInitScript = `(() => {
|
|
42
|
+
const ID = '__ea_auth_creds_bar__';
|
|
43
|
+
const text = ${JSON.stringify(`TEST LOGIN · ${LOGIN_EMAIL} · password: ${LOGIN_PASSWORD}`)};
|
|
44
|
+
const render = () => {
|
|
45
|
+
if (!document.documentElement) return;
|
|
46
|
+
let b = document.getElementById(ID);
|
|
47
|
+
if (!b) {
|
|
48
|
+
b = document.createElement('div');
|
|
49
|
+
b.id = ID;
|
|
50
|
+
b.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:2147483647;background:#0a7d3b;color:#fff;font:600 13px/26px ui-monospace,SFMono-Regular,Menlo,monospace;text-align:center;height:26px;padding:0 10px;box-shadow:0 1px 4px rgba(0,0,0,.35);pointer-events:none';
|
|
51
|
+
(document.body || document.documentElement).appendChild(b);
|
|
52
|
+
}
|
|
53
|
+
b.textContent = text;
|
|
54
|
+
};
|
|
55
|
+
render();
|
|
56
|
+
document.addEventListener('DOMContentLoaded', render);
|
|
57
|
+
setInterval(render, 800);
|
|
58
|
+
})();`;
|
|
59
|
+
|
|
60
|
+
async function main(): Promise<void> {
|
|
61
|
+
console.log('Opening Chrome. Please:');
|
|
62
|
+
console.log(` Log in as: ${LOGIN_EMAIL}`);
|
|
63
|
+
console.log(` Password: ${LOGIN_PASSWORD} (also in the green bar atop the browser)`);
|
|
64
|
+
console.log(' 1. Log in to Shopify Partners.');
|
|
65
|
+
console.log(' 2. Then visit your test store admin (e.g. https://admin.shopify.com/store/<your-store>) —');
|
|
66
|
+
console.log(' if a Cloudflare "Verify you are human" challenge appears, click through it.');
|
|
67
|
+
console.log(' Without this step, tests will hit Turnstile and fail.');
|
|
68
|
+
console.log(' 3. When both are done, return here and press Enter.');
|
|
69
|
+
console.log('');
|
|
70
|
+
|
|
71
|
+
// chromium.launch + newContext (matches storePool fixture model).
|
|
72
|
+
// Critically, this is NOT launchPersistentContext — the persistent
|
|
73
|
+
// profile accumulates fingerprint state that triggers Cloudflare's
|
|
74
|
+
// bot detection. Fresh-context model means the browser fingerprint
|
|
75
|
+
// CF sees here (during capture) is the SAME fingerprint it'll see
|
|
76
|
+
// at test time (when a fresh context is built from this saved
|
|
77
|
+
// storageState). cf_clearance is fingerprint-bound, so this
|
|
78
|
+
// consistency is what makes the saved cookie actually work.
|
|
79
|
+
//
|
|
80
|
+
// Patchright (no `channel: 'chrome'`) — uses its bundled
|
|
81
|
+
// chromium with stealth patches. Same browser as storePool +
|
|
82
|
+
// conformance probes, so cf_clearance carries across all three.
|
|
83
|
+
// Real Google Chrome was tried earlier but its fingerprint
|
|
84
|
+
// doesn't match patchright's, causing CF re-challenges.
|
|
85
|
+
const browser = await chromium.launch({
|
|
86
|
+
headless: false,
|
|
87
|
+
args: [
|
|
88
|
+
'--ip-address-space-overrides=127.0.0.1:0=public,[::1]:0=public',
|
|
89
|
+
'--disable-features=LocalNetworkAccessChecks,LocalNetworkAccessChecksWebSockets,LocalNetworkAccessChecksWebTransport,BlockInsecurePrivateNetworkRequests,PrivateNetworkAccessRespectPreflightResults,PrivateNetworkAccessSendPreflights',
|
|
90
|
+
'--local-network-access-permissions-policy-default-enabled',
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
const context = (await browser.newContext({
|
|
94
|
+
viewport: { width: 1400, height: 900 },
|
|
95
|
+
ignoreHTTPSErrors: true,
|
|
96
|
+
})) as unknown as BrowserContext;
|
|
97
|
+
|
|
98
|
+
// Pin the creds to a bar at the top of the (VNC'd) browser.
|
|
99
|
+
await context.addInitScript(credsBarInitScript);
|
|
100
|
+
|
|
101
|
+
const page = await context.newPage();
|
|
102
|
+
await page.goto('https://accounts.shopify.com/lookup');
|
|
103
|
+
|
|
104
|
+
await waitForEnter('Press Enter once logged in AND past any admin Turnstile... ');
|
|
105
|
+
|
|
106
|
+
// Sanity-check cf_clearance presence and warn loudly if absent —
|
|
107
|
+
// capture without it works for Partner-API-only flows, but online
|
|
108
|
+
// tests that drive admin.shopify.com will fail on Turnstile.
|
|
109
|
+
const cookies = await context.cookies();
|
|
110
|
+
const hasCfClearance = cookies.some(
|
|
111
|
+
(c) => c.name === 'cf_clearance' && c.domain.includes('shopify.com'),
|
|
112
|
+
);
|
|
113
|
+
if (!hasCfClearance) {
|
|
114
|
+
console.warn(
|
|
115
|
+
'⚠️ No cf_clearance cookie found for *.shopify.com. The online suite will hit Turnstile.',
|
|
116
|
+
);
|
|
117
|
+
console.warn(
|
|
118
|
+
' Visit your test store admin in this browser, pass the Cloudflare challenge, then re-run.',
|
|
119
|
+
);
|
|
120
|
+
} else {
|
|
121
|
+
console.log('✓ cf_clearance cookie captured (admin.shopify.com Turnstile bypass).');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
mkdirSync(dirname(storageStatePath), { recursive: true });
|
|
125
|
+
await context.storageState({ path: storageStatePath });
|
|
126
|
+
console.log(`✓ Auth state saved: ${storageStatePath}`);
|
|
127
|
+
|
|
128
|
+
await context.close();
|
|
129
|
+
await browser.close();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function waitForEnter(prompt: string): Promise<void> {
|
|
133
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
134
|
+
return new Promise((res) =>
|
|
135
|
+
rl.question(prompt, () => {
|
|
136
|
+
rl.close();
|
|
137
|
+
res();
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
main().catch((err) => {
|
|
143
|
+
console.error(err);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
});
|