@reshotdev/screenshot 0.0.1-beta.6 → 0.0.1-beta.8
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/README.md +9 -3
- package/package.json +1 -1
- package/src/commands/auth.js +1 -1
- package/src/commands/publish.js +60 -23
- package/src/commands/pull.js +25 -1
- package/src/commands/setup.js +32 -4
- package/src/index.js +18 -0
- package/src/lib/api-client.js +50 -5
- package/src/lib/capture-script-runner.js +48 -4
- package/src/lib/ui-api.js +4 -5
package/README.md
CHANGED
|
@@ -76,7 +76,6 @@ Create `reshot.config.json` in your project root:
|
|
|
76
76
|
| `reshot record [title]` | Interactive recording via Chrome DevTools | `--browser`, `--url`, `--port` |
|
|
77
77
|
| `reshot sync` | Upload traces/docs to Reshot platform | `--trace-dir`, `--dry-run` |
|
|
78
78
|
| `reshot studio` | Launch web management UI | `--port`, `--no-open` |
|
|
79
|
-
| `reshot validate` | Check config and bindings | `--strict`, `--fix` |
|
|
80
79
|
| `reshot status` | View project status and sync history | `--jobs`, `--drifts`, `--json` |
|
|
81
80
|
| `reshot publish` | Upload assets with versioning | `--tag`, `--message`, `--dry-run` |
|
|
82
81
|
| `reshot pull` | Generate asset map for builds | `--format json\|ts\|csv`, `--output`, `--status` |
|
|
@@ -225,12 +224,19 @@ The recorded scenario is appended to `reshot.config.json` automatically.
|
|
|
225
224
|
|
|
226
225
|
## Authentication
|
|
227
226
|
|
|
228
|
-
### Storage State
|
|
227
|
+
### Storage State
|
|
228
|
+
|
|
229
|
+
The CLI stores browser session state at `~/.reshot/session-state.json` (global).
|
|
230
|
+
This is automatically captured when you run `reshot record`.
|
|
231
|
+
|
|
232
|
+
To manually generate it:
|
|
229
233
|
|
|
230
234
|
```bash
|
|
231
|
-
npx playwright codegen http://localhost:3000 --save-storage
|
|
235
|
+
npx playwright codegen http://localhost:3000 --save-storage=$HOME/.reshot/session-state.json
|
|
232
236
|
```
|
|
233
237
|
|
|
238
|
+
Or reference a project-local path in your config:
|
|
239
|
+
|
|
234
240
|
```json
|
|
235
241
|
{
|
|
236
242
|
"storageStatePath": ".reshot/auth-state.json"
|
package/package.json
CHANGED
package/src/commands/auth.js
CHANGED
|
@@ -217,7 +217,7 @@ async function authCommand() {
|
|
|
217
217
|
await verifyApiKey(apiBaseUrl, status.project.apiKey);
|
|
218
218
|
|
|
219
219
|
// Derive platformUrl from apiBaseUrl (remove /api suffix)
|
|
220
|
-
const platformUrl = apiBaseUrl.replace(/\/api\/?$/, '') || '
|
|
220
|
+
const platformUrl = apiBaseUrl.replace(/\/api\/?$/, '') || 'https://reshot.dev';
|
|
221
221
|
|
|
222
222
|
writeSettings({
|
|
223
223
|
projectId: status.project.id,
|
package/src/commands/publish.js
CHANGED
|
@@ -578,6 +578,7 @@ async function publishWithTransactionalFlow(
|
|
|
578
578
|
docSyncConfig,
|
|
579
579
|
gitInfo,
|
|
580
580
|
diffManifests = null,
|
|
581
|
+
{ autoApprove = false } = {},
|
|
581
582
|
) {
|
|
582
583
|
console.log(
|
|
583
584
|
chalk.cyan(" 🚀 Using transactional upload (direct to R2)...\n"),
|
|
@@ -807,7 +808,11 @@ async function publishWithTransactionalFlow(
|
|
|
807
808
|
});
|
|
808
809
|
}
|
|
809
810
|
|
|
810
|
-
//
|
|
811
|
+
// Build all commits for batch request
|
|
812
|
+
// Vercel serverless functions have ~60s timeout; keep batches small enough to complete
|
|
813
|
+
const MAX_BATCH_SIZE = 25;
|
|
814
|
+
const commits = [];
|
|
815
|
+
|
|
811
816
|
for (const { group, scenarioConfig, assets } of groupMap.values()) {
|
|
812
817
|
const contextObj = buildContextForVariation(
|
|
813
818
|
scenarioConfig,
|
|
@@ -824,38 +829,65 @@ async function publishWithTransactionalFlow(
|
|
|
824
829
|
});
|
|
825
830
|
|
|
826
831
|
if (metadata.cli) {
|
|
827
|
-
metadata.cli.features = ["steps", "transactional"];
|
|
832
|
+
metadata.cli.features = ["steps", "transactional", "batch"];
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
commits.push({ metadata, assets });
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Send in batches
|
|
839
|
+
console.log(
|
|
840
|
+
chalk.gray(` Committing ${commits.length} scenario(s) to platform...`),
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
const totalBatches = Math.ceil(commits.length / MAX_BATCH_SIZE);
|
|
844
|
+
for (let i = 0; i < commits.length; i += MAX_BATCH_SIZE) {
|
|
845
|
+
const chunk = commits.slice(i, i + MAX_BATCH_SIZE);
|
|
846
|
+
const batchNum = Math.floor(i / MAX_BATCH_SIZE) + 1;
|
|
847
|
+
if (totalBatches > 1) {
|
|
848
|
+
console.log(chalk.gray(` Batch ${batchNum}/${totalBatches}...`));
|
|
828
849
|
}
|
|
829
850
|
|
|
830
851
|
try {
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
852
|
+
const rawBatchResult = await apiClient.publishBatch(apiKey, {
|
|
853
|
+
commits: chunk,
|
|
854
|
+
autoApprove: autoApprove || false,
|
|
834
855
|
});
|
|
856
|
+
// Unwrap API envelope: response may be { data: { results, ... } } or { results, ... }
|
|
857
|
+
const batchResult = rawBatchResult.data || rawBatchResult;
|
|
835
858
|
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
859
|
+
for (const r of batchResult.results || []) {
|
|
860
|
+
if (r.status === "ok") {
|
|
861
|
+
const count = r.assetsProcessed || 0;
|
|
862
|
+
console.log(
|
|
863
|
+
chalk.green(
|
|
864
|
+
` ✔ Committed "${r.scenario}" (${r.context}): ${count} asset(s)`,
|
|
865
|
+
),
|
|
866
|
+
);
|
|
867
|
+
successCount += count;
|
|
843
868
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
869
|
+
if (r.skippedAssets?.length > 0) {
|
|
870
|
+
for (const key of r.skippedAssets) {
|
|
871
|
+
console.log(chalk.yellow(` ⚠ Skipped "${key}" (plan limit reached)`));
|
|
872
|
+
}
|
|
873
|
+
skippedCount += r.skippedAssets.length;
|
|
874
|
+
}
|
|
875
|
+
} else {
|
|
876
|
+
console.log(
|
|
877
|
+
chalk.red(
|
|
878
|
+
` ✖ "${r.scenario}" (${r.context}): ${r.error || "Unknown error"}`,
|
|
879
|
+
),
|
|
880
|
+
);
|
|
881
|
+
failCount++;
|
|
848
882
|
}
|
|
849
|
-
skippedCount += result.skippedAssets.length;
|
|
850
883
|
}
|
|
851
884
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
viewUrl = result.viewUrl;
|
|
885
|
+
if (!viewUrl && batchResult.viewUrl) {
|
|
886
|
+
viewUrl = batchResult.viewUrl;
|
|
855
887
|
}
|
|
856
888
|
} catch (error) {
|
|
857
|
-
console.log(chalk.red(` ✖
|
|
858
|
-
failCount +=
|
|
889
|
+
console.log(chalk.red(` ✖ Batch request failed: ${error.message}`));
|
|
890
|
+
failCount += chunk.length;
|
|
859
891
|
}
|
|
860
892
|
}
|
|
861
893
|
|
|
@@ -1204,7 +1236,7 @@ function parseFrontmatter(content) {
|
|
|
1204
1236
|
}
|
|
1205
1237
|
|
|
1206
1238
|
async function publishCommand(options = {}) {
|
|
1207
|
-
const { tag, message, dryRun, force, video, outputJson } = options;
|
|
1239
|
+
const { tag, message, dryRun, force, video, outputJson, autoApprove } = options;
|
|
1208
1240
|
|
|
1209
1241
|
// Result tracking for --output-json and programmatic callers
|
|
1210
1242
|
const publishResult = {
|
|
@@ -1230,6 +1262,10 @@ async function publishCommand(options = {}) {
|
|
|
1230
1262
|
console.log(chalk.yellow("🔍 DRY RUN MODE - No assets will be uploaded\n"));
|
|
1231
1263
|
}
|
|
1232
1264
|
|
|
1265
|
+
if (autoApprove) {
|
|
1266
|
+
console.log(chalk.cyan(" ✅ Auto-approve enabled: visuals will be approved immediately\n"));
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1233
1269
|
// Read config + settings (if available)
|
|
1234
1270
|
const settings = readSettingsSafe();
|
|
1235
1271
|
let docSyncConfig = null;
|
|
@@ -1413,6 +1449,7 @@ async function publishCommand(options = {}) {
|
|
|
1413
1449
|
docSyncConfig,
|
|
1414
1450
|
{ commitHash, commitMessage, publishSessionId },
|
|
1415
1451
|
diffManifests,
|
|
1452
|
+
{ autoApprove },
|
|
1416
1453
|
);
|
|
1417
1454
|
successCount = result.successCount;
|
|
1418
1455
|
failCount = result.failCount;
|
package/src/commands/pull.js
CHANGED
|
@@ -142,6 +142,19 @@ async function pullCommand(options = {}) {
|
|
|
142
142
|
process.exit(1);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
if (settings.projectId && projectId !== settings.projectId) {
|
|
146
|
+
console.warn(
|
|
147
|
+
chalk.yellow(
|
|
148
|
+
` ⚠ Project ID mismatch: config uses ${projectId}, but authenticated project is ${settings.projectId}.`
|
|
149
|
+
)
|
|
150
|
+
);
|
|
151
|
+
console.warn(
|
|
152
|
+
chalk.yellow(
|
|
153
|
+
` This will cause auth failures. Run 'reshot setup --force' to re-link.\n`
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
145
158
|
try {
|
|
146
159
|
// Fetch the asset map from the API
|
|
147
160
|
console.log(chalk.gray(` Fetching assets for project: ${projectId}`));
|
|
@@ -163,7 +176,18 @@ async function pullCommand(options = {}) {
|
|
|
163
176
|
0
|
|
164
177
|
);
|
|
165
178
|
|
|
166
|
-
|
|
179
|
+
if (assetCount === 0) {
|
|
180
|
+
console.log(chalk.yellow(` ⚠ Fetched 0 visuals.\n`));
|
|
181
|
+
console.log(chalk.gray(` Possible reasons:`));
|
|
182
|
+
if (status === "approved") {
|
|
183
|
+
console.log(chalk.gray(` • No visuals approved yet. Try: reshot pull --status all`));
|
|
184
|
+
}
|
|
185
|
+
console.log(chalk.gray(` • No visuals published. Run: reshot publish`));
|
|
186
|
+
console.log(chalk.gray(` • Wrong project. Config uses: ${projectId}`));
|
|
187
|
+
console.log(chalk.gray(` • Check: ${settings.platformUrl || "https://reshot.dev"}\n`));
|
|
188
|
+
} else {
|
|
189
|
+
console.log(chalk.green(` ✓ Fetched ${assetCount} visuals\n`));
|
|
190
|
+
}
|
|
167
191
|
|
|
168
192
|
// Determine output path
|
|
169
193
|
let outputPath = output;
|
package/src/commands/setup.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
const chalk = require("chalk");
|
|
3
3
|
const oraModule = require("ora");
|
|
4
4
|
const ora = oraModule.default || oraModule;
|
|
5
|
+
const { execSync } = require("child_process");
|
|
5
6
|
|
|
6
7
|
const {
|
|
7
8
|
writeSettings,
|
|
@@ -100,10 +101,14 @@ async function setupCommand(options = {}) {
|
|
|
100
101
|
if (hasConfig && !force) {
|
|
101
102
|
console.log(chalk.green("✔ Configuration found:"), chalk.cyan("reshot.config.json"));
|
|
102
103
|
} else if (hasConfig && force) {
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
// Patch projectId in existing config instead of wiping scenarios
|
|
105
|
+
const fs = require("fs");
|
|
106
|
+
const path = require("path");
|
|
107
|
+
const configPath = path.join(process.cwd(), "reshot.config.json");
|
|
108
|
+
const existingConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
109
|
+
existingConfig.projectId = existingSettings.projectId;
|
|
110
|
+
fs.writeFileSync(configPath, JSON.stringify(existingConfig, null, 2) + "\n");
|
|
111
|
+
console.log(chalk.green("✔ Configuration updated (projectId synced)"));
|
|
107
112
|
} else {
|
|
108
113
|
// No config - create it
|
|
109
114
|
console.log(chalk.gray("Creating reshot.config.json..."));
|
|
@@ -112,6 +117,29 @@ async function setupCommand(options = {}) {
|
|
|
112
117
|
console.log(chalk.green("✔ Configuration created:"), chalk.cyan("reshot.config.json"));
|
|
113
118
|
}
|
|
114
119
|
|
|
120
|
+
// Step 3.5: Ensure @reshotdev/screenshot is in devDependencies
|
|
121
|
+
const fs = require("fs");
|
|
122
|
+
const path = require("path");
|
|
123
|
+
const pkgJsonPath = path.join(process.cwd(), "package.json");
|
|
124
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
125
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
126
|
+
const hasDep =
|
|
127
|
+
pkgJson.devDependencies?.["@reshotdev/screenshot"] ||
|
|
128
|
+
pkgJson.dependencies?.["@reshotdev/screenshot"];
|
|
129
|
+
if (!hasDep) {
|
|
130
|
+
console.log(chalk.gray(" Adding @reshotdev/screenshot to devDependencies..."));
|
|
131
|
+
const usePnpm = fs.existsSync(path.join(process.cwd(), "pnpm-lock.yaml"));
|
|
132
|
+
const useYarn = fs.existsSync(path.join(process.cwd(), "yarn.lock"));
|
|
133
|
+
const cmd = usePnpm ? "pnpm add -D" : useYarn ? "yarn add -D" : "npm install -D";
|
|
134
|
+
try {
|
|
135
|
+
execSync(`${cmd} @reshotdev/screenshot`, { stdio: "inherit" });
|
|
136
|
+
console.log(chalk.green(" ✔ Added @reshotdev/screenshot to devDependencies"));
|
|
137
|
+
} catch {
|
|
138
|
+
console.log(chalk.yellow(" ⚠ Could not auto-install. Run manually: " + cmd + " @reshotdev/screenshot"));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
115
143
|
// Step 4: Success summary
|
|
116
144
|
console.log(chalk.green("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
|
|
117
145
|
console.log(chalk.green.bold("✔ Project initialized successfully!"));
|
package/src/index.js
CHANGED
|
@@ -160,12 +160,28 @@ program
|
|
|
160
160
|
.option("--browser", "Launch Chrome with remote debugging before recording")
|
|
161
161
|
.option("-p, --port <port>", "Chrome debugging port (default: 9222)")
|
|
162
162
|
.option("--url <url>", "URL to open when launching browser")
|
|
163
|
+
.option("--refresh-session", "Only refresh the auth session (no recording prompts)")
|
|
163
164
|
.option("--debug", "Enable verbose debug logging")
|
|
164
165
|
.action(async (title, options) => {
|
|
165
166
|
if (options.debug) {
|
|
166
167
|
process.env.RESHOT_DEBUG = "1";
|
|
167
168
|
}
|
|
168
169
|
try {
|
|
170
|
+
// If --refresh-session, just sync the session and exit
|
|
171
|
+
if (options.refreshSession) {
|
|
172
|
+
const { autoSyncSessionFromCDP, getDefaultSessionPath } = require("./lib/record-cdp");
|
|
173
|
+
const sessionPath = getDefaultSessionPath();
|
|
174
|
+
console.log(chalk.gray(" Syncing session from active browser..."));
|
|
175
|
+
const synced = await autoSyncSessionFromCDP(sessionPath);
|
|
176
|
+
if (synced) {
|
|
177
|
+
console.log(chalk.green(" ✔ Session refreshed at " + sessionPath));
|
|
178
|
+
} else {
|
|
179
|
+
console.log(chalk.yellow(" ⚠ No active CDP browser found. Launch Chrome with remote debugging first:"));
|
|
180
|
+
console.log(chalk.gray(" reshot record --browser --refresh-session"));
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
169
185
|
// If --browser flag, launch Chrome first
|
|
170
186
|
if (options.browser) {
|
|
171
187
|
const chromeCommand = require("./commands/chrome");
|
|
@@ -221,12 +237,14 @@ program
|
|
|
221
237
|
.option("--dry-run", "Preview without uploading")
|
|
222
238
|
.option("-f, --force", "Skip confirmation prompts")
|
|
223
239
|
.option("--output-json", "Write structured result to .reshot/output/publish-result.json")
|
|
240
|
+
.option("--auto-approve", "Automatically approve published visuals (skip review queue)")
|
|
224
241
|
.action(async (options) => {
|
|
225
242
|
try {
|
|
226
243
|
const publishCommand = require("./commands/publish");
|
|
227
244
|
await publishCommand({
|
|
228
245
|
...options,
|
|
229
246
|
outputJson: options.outputJson,
|
|
247
|
+
autoApprove: options.autoApprove,
|
|
230
248
|
});
|
|
231
249
|
} catch (error) {
|
|
232
250
|
console.error(chalk.red("Error:"), error.message);
|
package/src/lib/api-client.js
CHANGED
|
@@ -3,15 +3,35 @@ const axios = require("axios");
|
|
|
3
3
|
const FormData = require("form-data");
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
process.env.RESHOT_API_BASE_URL ||
|
|
8
|
-
process.env.RESHOT_API_BASE_URL ||
|
|
9
|
-
"http://localhost:3000/api";
|
|
6
|
+
const PRODUCTION_API_URL = "https://reshot.dev/api";
|
|
10
7
|
|
|
11
8
|
function getApiBaseUrl() {
|
|
12
|
-
|
|
9
|
+
// 1. Explicit env var override (for CI or local dev)
|
|
10
|
+
if (process.env.RESHOT_API_BASE_URL) {
|
|
11
|
+
return process.env.RESHOT_API_BASE_URL;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 2. Read from settings.json (set during auth/setup)
|
|
15
|
+
try {
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const settingsPath = path.join(process.cwd(), ".reshot", "settings.json");
|
|
18
|
+
if (fs.existsSync(settingsPath)) {
|
|
19
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
20
|
+
if (settings.platformUrl) {
|
|
21
|
+
return settings.platformUrl.replace(/\/+$/, "") + "/api";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
// Settings don't exist yet (first auth) — fall through to default
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 3. Default to production
|
|
29
|
+
return PRODUCTION_API_URL;
|
|
13
30
|
}
|
|
14
31
|
|
|
32
|
+
// Resolved once at module load — all API calls use this
|
|
33
|
+
const baseUrl = getApiBaseUrl();
|
|
34
|
+
|
|
15
35
|
/**
|
|
16
36
|
* Sleep helper for retry delays
|
|
17
37
|
*/
|
|
@@ -586,6 +606,30 @@ async function publishTransactional(apiKey, payload) {
|
|
|
586
606
|
return response.data;
|
|
587
607
|
}
|
|
588
608
|
|
|
609
|
+
/**
|
|
610
|
+
* Batch publish metadata for multiple scenarios in one request
|
|
611
|
+
* @param {string} apiKey - API key for authentication
|
|
612
|
+
* @param {Object} payload - { commits: Array<{ metadata, assets }> }
|
|
613
|
+
* @returns {Promise<Object>}
|
|
614
|
+
*/
|
|
615
|
+
async function publishBatch(apiKey, payload) {
|
|
616
|
+
if (!apiKey) {
|
|
617
|
+
throw new Error("API key is required to publish");
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const response = await axios.post(`${baseUrl}/v1/publish/batch`, payload, {
|
|
621
|
+
headers: {
|
|
622
|
+
"Content-Type": "application/json",
|
|
623
|
+
Authorization: `Bearer ${apiKey}`,
|
|
624
|
+
},
|
|
625
|
+
timeout: 60000,
|
|
626
|
+
maxBodyLength: Infinity,
|
|
627
|
+
maxContentLength: Infinity,
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
return response.data;
|
|
631
|
+
}
|
|
632
|
+
|
|
589
633
|
/**
|
|
590
634
|
* Check which hashes already exist in storage (for deduplication)
|
|
591
635
|
* @param {string} apiKey - API key for authentication
|
|
@@ -800,6 +844,7 @@ module.exports = {
|
|
|
800
844
|
signAssets,
|
|
801
845
|
uploadToPresignedUrl,
|
|
802
846
|
publishTransactional,
|
|
847
|
+
publishBatch,
|
|
803
848
|
checkExistingHashes,
|
|
804
849
|
// Diffing support
|
|
805
850
|
getBaselines,
|
|
@@ -303,7 +303,18 @@ async function preflightAuthCheck(baseUrl, options = {}) {
|
|
|
303
303
|
};
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
|
|
306
|
+
// Save refreshed session back so scenarios use fresh cookies
|
|
307
|
+
if (storageStatePath && engine.context) {
|
|
308
|
+
try {
|
|
309
|
+
const refreshedState = await engine.context.storageState();
|
|
310
|
+
fs.writeFileSync(storageStatePath, JSON.stringify(refreshedState, null, 2));
|
|
311
|
+
console.log(chalk.green(" ✔ Auth pre-flight check passed (session refreshed)"));
|
|
312
|
+
} catch (_saveErr) {
|
|
313
|
+
console.log(chalk.green(" ✔ Auth pre-flight check passed"));
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
console.log(chalk.green(" ✔ Auth pre-flight check passed"));
|
|
317
|
+
}
|
|
307
318
|
return { ok: true };
|
|
308
319
|
} catch (e) {
|
|
309
320
|
// If the error is an auth redirect thrown by the engine, handle gracefully
|
|
@@ -1133,11 +1144,25 @@ async function runScenarioWithStepByStepCapture(scenario, options = {}) {
|
|
|
1133
1144
|
`Page error detected: ${errMsg}. The page rendered an error UI instead of expected content.`
|
|
1134
1145
|
);
|
|
1135
1146
|
} else if (retryResult.status === "timeout") {
|
|
1147
|
+
const currentUrl = engine.page.url();
|
|
1136
1148
|
console.log(
|
|
1137
1149
|
chalk.yellow(
|
|
1138
|
-
` ⚠ Ready selector not found after ${retryResult.attempts} attempt(s)
|
|
1150
|
+
` ⚠ Ready selector not found after ${retryResult.attempts} attempt(s): ${readySelector}`
|
|
1151
|
+
)
|
|
1152
|
+
);
|
|
1153
|
+
console.log(
|
|
1154
|
+
chalk.gray(` URL: ${currentUrl}`)
|
|
1155
|
+
);
|
|
1156
|
+
console.log(
|
|
1157
|
+
chalk.gray(
|
|
1158
|
+
` Hint: The page loaded but this selector does not exist. Check your readySelector in reshot.config.json.`
|
|
1139
1159
|
)
|
|
1140
1160
|
);
|
|
1161
|
+
throw new Error(
|
|
1162
|
+
`Scenario readySelector "${readySelector}" not found after ${retryResult.attempts} attempt(s). ` +
|
|
1163
|
+
`The page loaded at ${currentUrl} but the selector does not exist. ` +
|
|
1164
|
+
`Update readySelector in reshot.config.json or remove it to skip this check.`
|
|
1165
|
+
);
|
|
1141
1166
|
} else if (retryResult.attempts > 1) {
|
|
1142
1167
|
console.log(
|
|
1143
1168
|
chalk.green(
|
|
@@ -1296,6 +1321,12 @@ async function runScenarioWithStepByStepCapture(scenario, options = {}) {
|
|
|
1296
1321
|
` Hint: If data isn't loading, run 'reshot record' to refresh your session`
|
|
1297
1322
|
)
|
|
1298
1323
|
);
|
|
1324
|
+
failedSteps.push({
|
|
1325
|
+
stepIndex: stepIndex + 1,
|
|
1326
|
+
action: "waitFor",
|
|
1327
|
+
target: params.target,
|
|
1328
|
+
error: errMsg,
|
|
1329
|
+
});
|
|
1299
1330
|
}
|
|
1300
1331
|
} else if (waitResult.status === "timeout") {
|
|
1301
1332
|
if (!isOptional) {
|
|
@@ -1309,9 +1340,14 @@ async function runScenarioWithStepByStepCapture(scenario, options = {}) {
|
|
|
1309
1340
|
` Hint: If content isn't loading, run 'reshot record' to refresh your session`
|
|
1310
1341
|
)
|
|
1311
1342
|
);
|
|
1343
|
+
failedSteps.push({
|
|
1344
|
+
stepIndex: stepIndex + 1,
|
|
1345
|
+
action: "waitFor",
|
|
1346
|
+
target: params.target,
|
|
1347
|
+
error: `Element not found within ${waitTimeout}ms`,
|
|
1348
|
+
});
|
|
1312
1349
|
}
|
|
1313
1350
|
}
|
|
1314
|
-
// Continue with next steps - the scenario may still capture partial state
|
|
1315
1351
|
continue;
|
|
1316
1352
|
}
|
|
1317
1353
|
|
|
@@ -2185,8 +2221,16 @@ async function runScenarioWithVideoCapture(scenario, options = {}) {
|
|
|
2185
2221
|
});
|
|
2186
2222
|
} catch (e) {
|
|
2187
2223
|
if (!isOptional) {
|
|
2224
|
+
const currentUrl = page.url();
|
|
2225
|
+
console.warn(
|
|
2226
|
+
chalk.yellow(` ⚠ Element not found: ${params.target}`)
|
|
2227
|
+
);
|
|
2228
|
+
console.warn(chalk.gray(` URL: ${currentUrl}`));
|
|
2229
|
+
console.warn(chalk.gray(` Timeout: ${waitTimeout}ms`));
|
|
2188
2230
|
console.warn(
|
|
2189
|
-
chalk.
|
|
2231
|
+
chalk.gray(
|
|
2232
|
+
` Hint: Verify the selector exists on the page. Run 'reshot record' to inspect.`
|
|
2233
|
+
)
|
|
2190
2234
|
);
|
|
2191
2235
|
}
|
|
2192
2236
|
}
|
package/src/lib/ui-api.js
CHANGED
|
@@ -43,17 +43,16 @@ const {
|
|
|
43
43
|
* @returns {string} Platform URL
|
|
44
44
|
*/
|
|
45
45
|
function getPlatformUrl(settings) {
|
|
46
|
-
// Priority: settings.platformUrl > env var >
|
|
46
|
+
// Priority: settings.platformUrl > env var > production default
|
|
47
47
|
if (settings?.platformUrl) {
|
|
48
48
|
return settings.platformUrl;
|
|
49
49
|
}
|
|
50
|
-
const envUrl =
|
|
51
|
-
process.env.RESHOT_API_BASE_URL || process.env.RESHOT_API_BASE_URL;
|
|
50
|
+
const envUrl = process.env.RESHOT_API_BASE_URL;
|
|
52
51
|
if (envUrl) {
|
|
53
52
|
// Remove /api suffix if present to get platform URL
|
|
54
53
|
return envUrl.replace(/\/api\/?$/, "");
|
|
55
54
|
}
|
|
56
|
-
return "
|
|
55
|
+
return "https://reshot.dev";
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
/**
|
|
@@ -2852,7 +2851,7 @@ function attachApiRoutes(app, context) {
|
|
|
2852
2851
|
const apiBaseUrl = getApiBaseUrl();
|
|
2853
2852
|
// Derive platformUrl from apiBaseUrl (remove /api suffix)
|
|
2854
2853
|
const platformUrl =
|
|
2855
|
-
apiBaseUrl.replace(/\/api\/?$/, "") || "
|
|
2854
|
+
apiBaseUrl.replace(/\/api\/?$/, "") || "https://reshot.dev";
|
|
2856
2855
|
|
|
2857
2856
|
config.writeSettings({
|
|
2858
2857
|
projectId: project.id,
|