@validate.qa/runner 1.0.79 → 1.0.81
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/cli.js +183 -17
- package/dist/cli.mjs +183 -17
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1907,7 +1907,7 @@ function isValidLiveFrame(frame) {
|
|
|
1907
1907
|
return false;
|
|
1908
1908
|
return true;
|
|
1909
1909
|
}
|
|
1910
|
-
var readEnvInt, LIVE_FRAME_PUSH_MAX_BASE64_BYTES, LIVE_THUMBNAIL_MAX_BASE64_BYTES, LIVE_FRAME_MAX_DIMENSION, LIVE_FRAME_JPEG_QUALITY, LIVE_FRAME_MIN_PUSH_INTERVAL_MS, LIVE_FRAME_UNFOCUSED_INTERVAL_MS, LIVE_FRAME_STALE_MS, LIVE_FRAME_MAX_SESSIONS_PER_PROJECT, MOBILE_MJPEG_SETTINGS, MOBILE_MJPEG_FIRST_FRAME_TIMEOUT_MS, MOBILE_MJPEG_MAX_FRAME_BYTES;
|
|
1910
|
+
var readEnvInt, LIVE_FRAME_PUSH_MAX_BASE64_BYTES, LIVE_THUMBNAIL_MAX_BASE64_BYTES, LIVE_FRAME_MAX_DIMENSION, LIVE_FRAME_JPEG_QUALITY, LIVE_FRAME_MIN_PUSH_INTERVAL_MS, LIVE_FRAME_UNFOCUSED_INTERVAL_MS, LIVE_FRAME_IDLE_PUSH_INTERVAL_MS, LIVE_FRAME_STALE_MS, LIVE_FRAME_MAX_SESSIONS_PER_PROJECT, MOBILE_MJPEG_SETTINGS, MOBILE_MJPEG_FIRST_FRAME_TIMEOUT_MS, MOBILE_MJPEG_MAX_FRAME_BYTES;
|
|
1911
1911
|
var init_live_frame = __esm({
|
|
1912
1912
|
"../shared/dist/live-frame.js"() {
|
|
1913
1913
|
"use strict";
|
|
@@ -1923,6 +1923,7 @@ var init_live_frame = __esm({
|
|
|
1923
1923
|
LIVE_FRAME_JPEG_QUALITY = readEnvInt("LIVE_FRAME_JPEG_QUALITY", 55);
|
|
1924
1924
|
LIVE_FRAME_MIN_PUSH_INTERVAL_MS = readEnvInt("LIVE_FRAME_MIN_PUSH_INTERVAL_MS", 125);
|
|
1925
1925
|
LIVE_FRAME_UNFOCUSED_INTERVAL_MS = readEnvInt("LIVE_FRAME_UNFOCUSED_INTERVAL_MS", 2e3);
|
|
1926
|
+
LIVE_FRAME_IDLE_PUSH_INTERVAL_MS = readEnvInt("LIVE_FRAME_IDLE_PUSH_INTERVAL_MS", 5e3);
|
|
1926
1927
|
LIVE_FRAME_STALE_MS = readEnvInt("LIVE_FRAME_STALE_MS", 3e4);
|
|
1927
1928
|
LIVE_FRAME_MAX_SESSIONS_PER_PROJECT = readEnvInt("LIVE_FRAME_MAX_SESSIONS_PER_PROJECT", 32);
|
|
1928
1929
|
MOBILE_MJPEG_SETTINGS = {
|
|
@@ -6360,6 +6361,7 @@ __export(dist_exports, {
|
|
|
6360
6361
|
GROUP_LABELS: () => GROUP_LABELS,
|
|
6361
6362
|
LEGACY_TTS_MODEL_MAP: () => LEGACY_TTS_MODEL_MAP,
|
|
6362
6363
|
LEGACY_TTS_VOICE_ID_MAP: () => LEGACY_TTS_VOICE_ID_MAP,
|
|
6364
|
+
LIVE_FRAME_IDLE_PUSH_INTERVAL_MS: () => LIVE_FRAME_IDLE_PUSH_INTERVAL_MS,
|
|
6363
6365
|
LIVE_FRAME_JPEG_QUALITY: () => LIVE_FRAME_JPEG_QUALITY,
|
|
6364
6366
|
LIVE_FRAME_MAX_DIMENSION: () => LIVE_FRAME_MAX_DIMENSION,
|
|
6365
6367
|
LIVE_FRAME_MAX_SESSIONS_PER_PROJECT: () => LIVE_FRAME_MAX_SESSIONS_PER_PROJECT,
|
|
@@ -321051,6 +321053,7 @@ var require_primary_action_ledger = __commonJS({
|
|
|
321051
321053
|
exports2.isLedgerActive = isLedgerActive;
|
|
321052
321054
|
exports2.isGateEnforcing = isGateEnforcing;
|
|
321053
321055
|
exports2.isSteeringActive = isSteeringActive;
|
|
321056
|
+
exports2.isPrimaryActionCoverageGapEnabled = isPrimaryActionCoverageGapEnabled;
|
|
321054
321057
|
exports2.leadingVerb = leadingVerb;
|
|
321055
321058
|
exports2.classifyPrimaryAction = classifyPrimaryAction;
|
|
321056
321059
|
exports2.containerScopeOf = containerScopeOf;
|
|
@@ -321065,6 +321068,8 @@ var require_primary_action_ledger = __commonJS({
|
|
|
321065
321068
|
exports2.classifyActionOutcome = classifyActionOutcome;
|
|
321066
321069
|
exports2.detectPrimaryActions = detectPrimaryActions;
|
|
321067
321070
|
exports2.summarizeLedger = summarizeLedger;
|
|
321071
|
+
exports2.summarizeUnexercisedPrimaries = summarizeUnexercisedPrimaries;
|
|
321072
|
+
exports2.buildUnexercisedPrimaryCoverageGaps = buildUnexercisedPrimaryCoverageGaps;
|
|
321068
321073
|
var reveal_diff_js_1 = require_reveal_diff();
|
|
321069
321074
|
var exploration_parser_js_1 = require_exploration_parser();
|
|
321070
321075
|
function getPrimaryActionGateMode(env = process.env) {
|
|
@@ -321084,6 +321089,10 @@ var require_primary_action_ledger = __commonJS({
|
|
|
321084
321089
|
function isSteeringActive(mode) {
|
|
321085
321090
|
return mode === "advisory" || mode === "enforce";
|
|
321086
321091
|
}
|
|
321092
|
+
function isPrimaryActionCoverageGapEnabled(env = process.env) {
|
|
321093
|
+
const raw = (env.DISCOVERY_PRIMARY_ACTION_COVERAGE_GAP ?? "").trim().toLowerCase();
|
|
321094
|
+
return raw === "1" || raw === "true" || raw === "on" || raw === "enabled";
|
|
321095
|
+
}
|
|
321087
321096
|
exports2.PRIMARY_ACTION_VERB_RE = /^(run|analyze|analyse|generate|regenerate|process|submit|save|create|send|upload|search|export|import|calculate|recalculate|compute|convert|start|scan|translate|transcribe|render|publish|sync|deploy|enrich|summar(?:ize|ise))\b/i;
|
|
321088
321097
|
exports2.AMBIGUOUS_ACTION_VERB_RE = /^(continue|next|apply|go|proceed|confirm|update|add|edit|ok|done|finish|complete|approve|enable|activate)\b/i;
|
|
321089
321098
|
exports2.DESTRUCTIVE_ACTION_VERB_RE = /^(delete|remove|archive|reset|disconnect|deactivate|destroy|purge|revoke|discard|drop|wipe|erase|unsubscribe|cancel|clear|trash)\b/i;
|
|
@@ -321347,6 +321356,47 @@ var require_primary_action_ledger = __commonJS({
|
|
|
321347
321356
|
}
|
|
321348
321357
|
return `actions=${c.total} primary{detected=${c.detected} attempted=${c.attempted} captured=${c.captured} blocked=${c.blocked} exempt=${c.exempt}} ambiguous=${c.ambiguous}`;
|
|
321349
321358
|
}
|
|
321359
|
+
function summarizeUnexercisedPrimaries(ledger, routeInScope, cap = 12) {
|
|
321360
|
+
const out = [];
|
|
321361
|
+
for (const e of getBlockingActionDebt(ledger, routeInScope)) {
|
|
321362
|
+
if (e.status !== "DETECTED" && e.status !== "ATTEMPTED")
|
|
321363
|
+
continue;
|
|
321364
|
+
out.push({ label: e.label, route: e.route, containerScope: e.containerScope, status: e.status });
|
|
321365
|
+
if (out.length >= cap)
|
|
321366
|
+
break;
|
|
321367
|
+
}
|
|
321368
|
+
return out;
|
|
321369
|
+
}
|
|
321370
|
+
var REGEXP_META_RE = /[.*+?^${}()|[\]\\]/g;
|
|
321371
|
+
function labelCoveredByPlan(normLabel, plannedLower) {
|
|
321372
|
+
if (!normLabel.includes(" "))
|
|
321373
|
+
return false;
|
|
321374
|
+
const escaped = normLabel.replace(REGEXP_META_RE, "\\$&");
|
|
321375
|
+
return new RegExp(`(^|\\W)${escaped}($|\\W)`).test(plannedLower);
|
|
321376
|
+
}
|
|
321377
|
+
function buildUnexercisedPrimaryCoverageGaps(unexercised, plannedScenarioText, nowIso) {
|
|
321378
|
+
const gaps = [];
|
|
321379
|
+
const seen = /* @__PURE__ */ new Set();
|
|
321380
|
+
const planned = plannedScenarioText.toLowerCase();
|
|
321381
|
+
for (const a of unexercised) {
|
|
321382
|
+
const norm = normLabelKey(a.label);
|
|
321383
|
+
const key = `${norm}|${a.route}`;
|
|
321384
|
+
if (seen.has(key))
|
|
321385
|
+
continue;
|
|
321386
|
+
seen.add(key);
|
|
321387
|
+
if (labelCoveredByPlan(norm, planned))
|
|
321388
|
+
continue;
|
|
321389
|
+
const clickedNote = a.status === "ATTEMPTED" ? "clicked, but no result state was captured" : "never clicked";
|
|
321390
|
+
gaps.push({
|
|
321391
|
+
scenarioName: `Exercise "${a.label}"`,
|
|
321392
|
+
reason: `Primary action "${a.label}" was detected on ${a.route} but never exercised to an observed result (${clickedNote}).`,
|
|
321393
|
+
suggestion: `Exercise "${a.label}" to a captured result \u2014 open its result modal/panel and capture it BEFORE closing \u2014 or schedule a deep run scoped to this action, so a real test can be authored.`,
|
|
321394
|
+
unmetPrerequisites: [],
|
|
321395
|
+
detectedAt: nowIso
|
|
321396
|
+
});
|
|
321397
|
+
}
|
|
321398
|
+
return gaps;
|
|
321399
|
+
}
|
|
321350
321400
|
}
|
|
321351
321401
|
});
|
|
321352
321402
|
|
|
@@ -322835,6 +322885,9 @@ var require_exploration_prompt_builder = __commonJS({
|
|
|
322835
322885
|
"These are the feature's core actions \u2014 what it EXISTS to do. You are on a PROVISIONED TEST ACCOUNT, so exercising them is the point of this exploration. For each: click it, wait for it to finish (an async job can take 10-60s \u2014 keep waiting and capturing, do not give up early), then capture_page the result (a new panel, row, score, status, or route change).",
|
|
322836
322886
|
`Spending in-app COMPUTE credits is expected and budgeted \u2014 running an Analyze / Generate / Run / Process ONCE to observe its output is exactly what you should do; an "N of M used" usage or AI-credit meter is NOT a reason to skip. These were auto-screened, but YOU are the final check: if you can see a specific listed action actually charges REAL money at a checkout (Buy / Upgrade / Subscribe), makes an irreversible or destructive change (Delete / Wipe / Deploy to production), or sends a message to real people (Send email / Invite), SKIP just that one \u2014 note it as blocked with the reason and move on. Don't finish until every other action below has produced an observed outcome:`
|
|
322837
322887
|
];
|
|
322888
|
+
if ((0, primary_action_ledger_js_1.isPrimaryActionCoverageGapEnabled)()) {
|
|
322889
|
+
out.push("If the result opens in a modal, dialog, or panel, call capture_page WHILE IT IS STILL OPEN \u2014 do not close or dismiss it first, or the result state is never recorded and the action does not count as covered. An action reachable only from inside this feature's own result modal is in-scope: exercise it to a captured result before closing, even if it generates content that belongs to another builder.");
|
|
322890
|
+
}
|
|
322838
322891
|
for (const e of debt.slice(0, 8)) {
|
|
322839
322892
|
const note = e.status === "ATTEMPTED" ? " \u2014 clicked but no result captured yet" : "";
|
|
322840
322893
|
out.push(` - "${e.label}" on ${e.route}${note}`);
|
|
@@ -326655,6 +326708,7 @@ ${inbox_tools_js_1.EMAIL_VERIFICATION_UNAVAILABLE_OVERRIDE_BLOCK}` : ""}`;
|
|
|
326655
326708
|
const primaryActionMode = (0, primary_action_ledger_js_1.getPrimaryActionGateMode)();
|
|
326656
326709
|
const primaryActionLedgerActive = (0, primary_action_ledger_js_1.isLedgerActive)(primaryActionMode) && options?.mode === "deep";
|
|
326657
326710
|
const primaryActionEnforcing = primaryActionLedgerActive && (0, primary_action_ledger_js_1.isGateEnforcing)(primaryActionMode);
|
|
326711
|
+
const primaryActionCoverageGapEnabled = primaryActionLedgerActive && (0, primary_action_ledger_js_1.isPrimaryActionCoverageGapEnabled)();
|
|
326658
326712
|
const prevRouteSignatures = /* @__PURE__ */ new Map();
|
|
326659
326713
|
const prevInteractionCountsByRoute = /* @__PURE__ */ new Map();
|
|
326660
326714
|
let previousCaptureRoute = null;
|
|
@@ -326665,6 +326719,16 @@ ${inbox_tools_js_1.EMAIL_VERIFICATION_UNAVAILABLE_OVERRIDE_BLOCK}` : ""}`;
|
|
|
326665
326719
|
let finishBounce = (0, survey_link_frontier_js_1.initialFinishBounceState)();
|
|
326666
326720
|
let lastLandedPageUrl = null;
|
|
326667
326721
|
const state2 = (0, exploration_state_js_1.createExplorationState)();
|
|
326722
|
+
const buildUnexercisedPrimarySummary = () => {
|
|
326723
|
+
if (!primaryActionCoverageGapEnabled)
|
|
326724
|
+
return void 0;
|
|
326725
|
+
const fa = options?.focusAreas ?? [];
|
|
326726
|
+
if (fa.length === 0)
|
|
326727
|
+
return void 0;
|
|
326728
|
+
const otherRoutes = options?.featureContext?.otherFeatures?.flatMap((f) => f.pages);
|
|
326729
|
+
const summary2 = (0, primary_action_ledger_js_1.summarizeUnexercisedPrimaries)(state2.actionLedger, (r) => (0, exploration_parser_js_1.routeIsWithinFocusAreas)(r, fa, otherRoutes), 12);
|
|
326730
|
+
return summary2.length > 0 ? summary2 : void 0;
|
|
326731
|
+
};
|
|
326668
326732
|
function ingestLinksFromText(rawText) {
|
|
326669
326733
|
if (!linkFrontierEnabled || !rawText || !rawText.includes("- /url:"))
|
|
326670
326734
|
return;
|
|
@@ -328895,6 +328959,7 @@ Exploration ended after ${iteration} iterations without calling finish_explorati
|
|
|
328895
328959
|
summary: state2.summary || summary,
|
|
328896
328960
|
collectedPages: state2.collectedPages,
|
|
328897
328961
|
collectedTransitions: state2.collectedTransitions,
|
|
328962
|
+
unexercisedPrimaryActions: buildUnexercisedPrimarySummary(),
|
|
328898
328963
|
iterationTimeline: iterationTimeline.length > 0 ? iterationTimeline : void 0
|
|
328899
328964
|
};
|
|
328900
328965
|
}
|
|
@@ -328989,6 +329054,7 @@ Fatal error: ${msg}`);
|
|
|
328989
329054
|
summary: `Fatal error: ${msg}`,
|
|
328990
329055
|
collectedPages: state2.collectedPages,
|
|
328991
329056
|
collectedTransitions: state2.collectedTransitions,
|
|
329057
|
+
unexercisedPrimaryActions: buildUnexercisedPrimarySummary(),
|
|
328992
329058
|
iterationTimeline: iterationTimeline.length > 0 ? iterationTimeline : void 0,
|
|
328993
329059
|
truncated: true,
|
|
328994
329060
|
truncatedReason,
|
|
@@ -337175,6 +337241,7 @@ var require_discover_agent = __commonJS({
|
|
|
337175
337241
|
var discover_planner_js_1 = require_discover_planner();
|
|
337176
337242
|
var discover_generator_js_1 = require_discover_generator();
|
|
337177
337243
|
var discover_ai_planner_js_1 = require_discover_ai_planner();
|
|
337244
|
+
var primary_action_ledger_js_1 = require_primary_action_ledger();
|
|
337178
337245
|
var discovery_lint_flags_js_1 = require_discovery_lint_flags();
|
|
337179
337246
|
var verification_inbox_js_1 = require_verification_inbox();
|
|
337180
337247
|
var inbox_tools_js_1 = require_inbox_tools();
|
|
@@ -338477,6 +338544,16 @@ Plan: ${planResult.scenarios.length} scenarios (budget: ${featureTestBudget})`);
|
|
|
338477
338544
|
checkpointFallbackReason = planResult.fallbackReason;
|
|
338478
338545
|
plannerWarning = planResult.warning;
|
|
338479
338546
|
plannerCoverageGaps = planResult.coverageGaps;
|
|
338547
|
+
const unexercisedPrimaries = exploreResult.unexercisedPrimaryActions ?? [];
|
|
338548
|
+
if (unexercisedPrimaries.length > 0) {
|
|
338549
|
+
const plannedText = planResult.scenarios.map((s) => `${s.name} ${s.goal ?? ""} ${(s.targetPages ?? []).join(" ")}`).join(" ");
|
|
338550
|
+
const ledgerGaps = (0, primary_action_ledger_js_1.buildUnexercisedPrimaryCoverageGaps)(unexercisedPrimaries, plannedText, (/* @__PURE__ */ new Date()).toISOString());
|
|
338551
|
+
if (ledgerGaps.length > 0) {
|
|
338552
|
+
plannerCoverageGaps = [...plannerCoverageGaps ?? [], ...ledgerGaps];
|
|
338553
|
+
}
|
|
338554
|
+
const suppressed = unexercisedPrimaries.length - ledgerGaps.length;
|
|
338555
|
+
logs2.push(` [primary-action-ledger] ${unexercisedPrimaries.length} in-scope primary action(s) un-exercised; surfaced ${ledgerGaps.length} as coverage gap(s)${suppressed > 0 ? ` (${suppressed} already covered by the plan / deduped)` : ""}`);
|
|
338556
|
+
}
|
|
338480
338557
|
}
|
|
338481
338558
|
if (callbackOptions?.onPlanReady) {
|
|
338482
338559
|
try {
|
|
@@ -353260,7 +353337,7 @@ var require_package4 = __commonJS({
|
|
|
353260
353337
|
"package.json"(exports2, module2) {
|
|
353261
353338
|
module2.exports = {
|
|
353262
353339
|
name: "@validate.qa/runner",
|
|
353263
|
-
version: "1.0.
|
|
353340
|
+
version: "1.0.81",
|
|
353264
353341
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
353265
353342
|
bin: {
|
|
353266
353343
|
"validate-runner": "dist/cli.js"
|
|
@@ -353529,6 +353606,9 @@ async function enforceRunnerUpToDate(currentVersion, opts = {}) {
|
|
|
353529
353606
|
return result;
|
|
353530
353607
|
}
|
|
353531
353608
|
|
|
353609
|
+
// src/runner.ts
|
|
353610
|
+
init_dist();
|
|
353611
|
+
|
|
353532
353612
|
// src/services/appium-executor.ts
|
|
353533
353613
|
init_dist();
|
|
353534
353614
|
|
|
@@ -358890,6 +358970,7 @@ function startMjpegStream(options) {
|
|
|
358890
358970
|
// src/services/mobile-bridge.ts
|
|
358891
358971
|
init_dist();
|
|
358892
358972
|
var MOBILE_BRIDGE_PORT2 = 9515;
|
|
358973
|
+
var RELAY_DEMAND_POLL_MS = 2e3;
|
|
358893
358974
|
var MAX_REQUEST_BODY_BYTES = 64 * 1024;
|
|
358894
358975
|
var MAX_TEXT_INPUT_LENGTH = 4e3;
|
|
358895
358976
|
var MAX_APP_ID_LENGTH = 255;
|
|
@@ -359401,6 +359482,18 @@ async function handleSessionStop(_req, res) {
|
|
|
359401
359482
|
sendJSON(res, 200, { ok: true });
|
|
359402
359483
|
}
|
|
359403
359484
|
var runnerChannels = /* @__PURE__ */ new Map();
|
|
359485
|
+
var relayDemandTimer = null;
|
|
359486
|
+
function setMirrorRelay(deviceId, relay) {
|
|
359487
|
+
const ch = runnerChannels.get(deviceId);
|
|
359488
|
+
if (!ch) return;
|
|
359489
|
+
ch.relay = relay;
|
|
359490
|
+
if (relay) {
|
|
359491
|
+
ensureRelayDemandSupervisor();
|
|
359492
|
+
if (hasChannelDemand(ch)) ensureChannelPolling(ch);
|
|
359493
|
+
} else {
|
|
359494
|
+
stopChannelCaptureIfIdle(ch);
|
|
359495
|
+
}
|
|
359496
|
+
}
|
|
359404
359497
|
function readDeviceIdParam(req) {
|
|
359405
359498
|
try {
|
|
359406
359499
|
const url = new URL(req.url || "", "http://localhost");
|
|
@@ -359432,7 +359525,7 @@ function writeFrameToChannelClient(ch, client, frame) {
|
|
|
359432
359525
|
} catch {
|
|
359433
359526
|
}
|
|
359434
359527
|
ch.clients.delete(client);
|
|
359435
|
-
|
|
359528
|
+
stopChannelCaptureIfIdle(ch);
|
|
359436
359529
|
return;
|
|
359437
359530
|
}
|
|
359438
359531
|
client.write(`data: ${JSON.stringify(frame)}
|
|
@@ -359444,6 +359537,12 @@ function writeFrameToChannelClient(ch, client, frame) {
|
|
|
359444
359537
|
function broadcastChannelFrame(ch, frame) {
|
|
359445
359538
|
ch.lastFrame = frame;
|
|
359446
359539
|
for (const client of ch.clients) writeFrameToChannelClient(ch, client, frame);
|
|
359540
|
+
if (ch.relay && frame.screenshot) {
|
|
359541
|
+
try {
|
|
359542
|
+
ch.relay.onFrame(frame.screenshot, frame.mimeType ?? "image/png");
|
|
359543
|
+
} catch {
|
|
359544
|
+
}
|
|
359545
|
+
}
|
|
359447
359546
|
}
|
|
359448
359547
|
function tryStartChannelMjpeg(ch) {
|
|
359449
359548
|
if (ch.mjpeg || ch.mjpegStarting || ch.mjpegUnavailable) return;
|
|
@@ -359481,7 +359580,7 @@ function tryStartChannelMjpeg(ch) {
|
|
|
359481
359580
|
ch.mjpeg = null;
|
|
359482
359581
|
ch.mjpegStarting = false;
|
|
359483
359582
|
ch.mjpegUnavailable = true;
|
|
359484
|
-
if (wasActive && ch
|
|
359583
|
+
if (wasActive && hasChannelDemand(ch)) ensureChannelPolling(ch);
|
|
359485
359584
|
}
|
|
359486
359585
|
});
|
|
359487
359586
|
const firstFrameDeadline = setTimeout(() => {
|
|
@@ -359489,12 +359588,30 @@ function tryStartChannelMjpeg(ch) {
|
|
|
359489
359588
|
ch.mjpegStarting = false;
|
|
359490
359589
|
ch.mjpegUnavailable = true;
|
|
359491
359590
|
handle.stop();
|
|
359492
|
-
if (ch
|
|
359591
|
+
if (hasChannelDemand(ch)) ensureChannelPolling(ch);
|
|
359493
359592
|
}, MOBILE_MJPEG_FIRST_FRAME_TIMEOUT_MS);
|
|
359494
359593
|
firstFrameDeadline.unref?.();
|
|
359495
359594
|
}
|
|
359595
|
+
function hasChannelDemand(ch) {
|
|
359596
|
+
if (ch.clients.size > 0) return true;
|
|
359597
|
+
return ch.relay !== null;
|
|
359598
|
+
}
|
|
359599
|
+
function ensureRelayDemandSupervisor() {
|
|
359600
|
+
if (relayDemandTimer) return;
|
|
359601
|
+
relayDemandTimer = setInterval(() => {
|
|
359602
|
+
for (const ch of [...runnerChannels.values()]) {
|
|
359603
|
+
if (hasChannelDemand(ch)) ensureChannelPolling(ch);
|
|
359604
|
+
else stopChannelCaptureIfIdle(ch);
|
|
359605
|
+
}
|
|
359606
|
+
if (runnerChannels.size === 0 && relayDemandTimer) {
|
|
359607
|
+
clearInterval(relayDemandTimer);
|
|
359608
|
+
relayDemandTimer = null;
|
|
359609
|
+
}
|
|
359610
|
+
}, RELAY_DEMAND_POLL_MS);
|
|
359611
|
+
relayDemandTimer.unref?.();
|
|
359612
|
+
}
|
|
359496
359613
|
async function pollRunnerChannel(ch) {
|
|
359497
|
-
if (ch
|
|
359614
|
+
if (!hasChannelDemand(ch) || ch.polling) return;
|
|
359498
359615
|
if (ch.mjpeg) return;
|
|
359499
359616
|
ch.polling = true;
|
|
359500
359617
|
try {
|
|
@@ -359525,6 +359642,7 @@ async function pollRunnerChannel(ch) {
|
|
|
359525
359642
|
}
|
|
359526
359643
|
}
|
|
359527
359644
|
function ensureChannelPolling(ch) {
|
|
359645
|
+
if (!hasChannelDemand(ch)) return;
|
|
359528
359646
|
tryStartChannelMjpeg(ch);
|
|
359529
359647
|
if (ch.mjpeg || ch.pollTimer) return;
|
|
359530
359648
|
ch.pollTimer = setInterval(() => {
|
|
@@ -359532,8 +359650,8 @@ function ensureChannelPolling(ch) {
|
|
|
359532
359650
|
}, MOBILE_SCREENSHOT_INTERVAL_MS);
|
|
359533
359651
|
ch.pollTimer.unref?.();
|
|
359534
359652
|
}
|
|
359535
|
-
function
|
|
359536
|
-
if (ch
|
|
359653
|
+
function stopChannelCaptureIfIdle(ch) {
|
|
359654
|
+
if (hasChannelDemand(ch)) return;
|
|
359537
359655
|
if (ch.pollTimer) {
|
|
359538
359656
|
clearInterval(ch.pollTimer);
|
|
359539
359657
|
ch.pollTimer = null;
|
|
@@ -359552,6 +359670,7 @@ function closeRunnerChannel(ch) {
|
|
|
359552
359670
|
ch.mjpeg.stop();
|
|
359553
359671
|
ch.mjpeg = null;
|
|
359554
359672
|
}
|
|
359673
|
+
ch.relay = null;
|
|
359555
359674
|
for (const client of ch.clients) {
|
|
359556
359675
|
try {
|
|
359557
359676
|
client.end();
|
|
@@ -359560,7 +359679,7 @@ function closeRunnerChannel(ch) {
|
|
|
359560
359679
|
}
|
|
359561
359680
|
ch.clients.clear();
|
|
359562
359681
|
ch.lastFrame = null;
|
|
359563
|
-
runnerChannels.delete(ch.deviceId);
|
|
359682
|
+
if (runnerChannels.get(ch.deviceId) === ch) runnerChannels.delete(ch.deviceId);
|
|
359564
359683
|
}
|
|
359565
359684
|
function closeAllRunnerChannels() {
|
|
359566
359685
|
for (const ch of [...runnerChannels.values()]) closeRunnerChannel(ch);
|
|
@@ -359582,7 +359701,7 @@ async function serveRunnerMirror(req, res, ch) {
|
|
|
359582
359701
|
}
|
|
359583
359702
|
req.on("close", () => {
|
|
359584
359703
|
ch.clients.delete(res);
|
|
359585
|
-
|
|
359704
|
+
stopChannelCaptureIfIdle(ch);
|
|
359586
359705
|
});
|
|
359587
359706
|
}
|
|
359588
359707
|
async function handleMirror(req, res) {
|
|
@@ -360140,7 +360259,8 @@ function attachMirrorSession(sessionId, platform3, deviceId) {
|
|
|
360140
360259
|
mjpeg: null,
|
|
360141
360260
|
mjpegLastFrameAt: 0,
|
|
360142
360261
|
mjpegStarting: false,
|
|
360143
|
-
mjpegUnavailable: false
|
|
360262
|
+
mjpegUnavailable: false,
|
|
360263
|
+
relay: null
|
|
360144
360264
|
};
|
|
360145
360265
|
ch.sessionId = sessionId;
|
|
360146
360266
|
ch.platform = platform3;
|
|
@@ -360154,7 +360274,7 @@ function attachMirrorSession(sessionId, platform3, deviceId) {
|
|
|
360154
360274
|
runnerChannels.set(deviceId, ch);
|
|
360155
360275
|
void applyMjpegSettings(`/session/${sessionId}`, MOBILE_MJPEG_SETTINGS).catch(() => {
|
|
360156
360276
|
});
|
|
360157
|
-
if (ch
|
|
360277
|
+
if (hasChannelDemand(ch)) {
|
|
360158
360278
|
ensureChannelPolling(ch);
|
|
360159
360279
|
void pollRunnerChannel(ch);
|
|
360160
360280
|
}
|
|
@@ -361126,6 +361246,13 @@ function attachMobileDiscoveryNetworkContext(calls, result, appId) {
|
|
|
361126
361246
|
}));
|
|
361127
361247
|
}
|
|
361128
361248
|
var runnerPackage = require_package4();
|
|
361249
|
+
var MIRROR_RELAY_FRESH_MS = 3e3;
|
|
361250
|
+
var liveFrameWatchedByProject = /* @__PURE__ */ new Map();
|
|
361251
|
+
var liveFrameIdleProbeAt = /* @__PURE__ */ new Map();
|
|
361252
|
+
var LIVE_FRAME_IDLE_PROBE_MAX_ENTRIES = 256;
|
|
361253
|
+
function isLiveFrameWatched(projectId) {
|
|
361254
|
+
return !projectId || liveFrameWatchedByProject.get(projectId) !== false;
|
|
361255
|
+
}
|
|
361129
361256
|
var SECRET_VAR_PATTERN = /password|secret|token|api[_]?key|pass\b|credential|bearer|pat\b/i;
|
|
361130
361257
|
var IS_CLOUD = parseBoolFlag(process.env.RUNNER_CLOUD);
|
|
361131
361258
|
var RUNNER_NAME = runnerPackage.name ?? "@validate.qa/runner";
|
|
@@ -361903,6 +362030,22 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
361903
362030
|
testCaseId: run2.testCaseId ?? void 0
|
|
361904
362031
|
});
|
|
361905
362032
|
requestLiveBrowserHeartbeat(true);
|
|
362033
|
+
let lastMirrorRelayFrameAt = 0;
|
|
362034
|
+
const attachMirrorWithRelay = (sessionId, platform3, deviceId) => {
|
|
362035
|
+
attachMirrorSession(sessionId, platform3, deviceId);
|
|
362036
|
+
if (!deviceId) return;
|
|
362037
|
+
setMirrorRelay(deviceId, {
|
|
362038
|
+
onFrame: (base64, mimeType) => {
|
|
362039
|
+
lastMirrorRelayFrameAt = Date.now();
|
|
362040
|
+
liveBrowserHandle.updateSnapshot({
|
|
362041
|
+
thumbnailJpegBase64: base64,
|
|
362042
|
+
thumbnailCapturedAt: Date.now(),
|
|
362043
|
+
mimeType
|
|
362044
|
+
});
|
|
362045
|
+
},
|
|
362046
|
+
wanted: () => isLiveFrameWatched(run2.projectId)
|
|
362047
|
+
});
|
|
362048
|
+
};
|
|
361906
362049
|
const liveBrowserHeartbeatInterval = setInterval(() => {
|
|
361907
362050
|
liveBrowserHandle.patch({});
|
|
361908
362051
|
requestLiveBrowserHeartbeat();
|
|
@@ -362806,7 +362949,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
362806
362949
|
deeplink: harvestTarget.deeplink
|
|
362807
362950
|
},
|
|
362808
362951
|
onSessionReady: (sessionId, deviceId, sessionPlatform) => {
|
|
362809
|
-
|
|
362952
|
+
attachMirrorWithRelay(sessionId, sessionPlatform, deviceId);
|
|
362810
362953
|
const name = getCachedDeviceSnapshot().find((d) => d.id === deviceId)?.name;
|
|
362811
362954
|
liveBrowserHandle.patch({ deviceId, ...name ? { deviceName: name } : {} });
|
|
362812
362955
|
requestLiveBrowserHeartbeat(true);
|
|
@@ -363276,7 +363419,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363276
363419
|
}
|
|
363277
363420
|
);
|
|
363278
363421
|
mobileOnLog(`Appium session created: ${session.sessionId} on device ${session.deviceId}`);
|
|
363279
|
-
|
|
363422
|
+
attachMirrorWithRelay(session.sessionId, platform3, session.deviceId);
|
|
363280
363423
|
const driver = new MobileBridgeDriver({
|
|
363281
363424
|
sessionId: session.sessionId,
|
|
363282
363425
|
platform: platform3,
|
|
@@ -363313,7 +363456,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363313
363456
|
throw new Error("discovery teardown started during session recovery \u2014 replacement session closed");
|
|
363314
363457
|
}
|
|
363315
363458
|
session = replacement;
|
|
363316
|
-
|
|
363459
|
+
attachMirrorWithRelay(replacement.sessionId, platform3, replacement.deviceId);
|
|
363317
363460
|
liveBrowserHandle.patch({
|
|
363318
363461
|
deviceId: replacement.deviceId,
|
|
363319
363462
|
deviceName: replacement.deviceName
|
|
@@ -363334,6 +363477,10 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363334
363477
|
// UI a reliable mobile fallback when the direct bridge SSE is
|
|
363335
363478
|
// still opening or blocked by the user's network/tunnel.
|
|
363336
363479
|
onScreenshot: (base64) => {
|
|
363480
|
+
if (Date.now() - lastMirrorRelayFrameAt < MIRROR_RELAY_FRESH_MS) {
|
|
363481
|
+
liveBrowserHandle.updateSnapshot({ currentUrl: mobileTarget.appId });
|
|
363482
|
+
return;
|
|
363483
|
+
}
|
|
363337
363484
|
liveBrowserHandle.updateSnapshot({
|
|
363338
363485
|
currentUrl: mobileTarget.appId,
|
|
363339
363486
|
thumbnailJpegBase64: base64,
|
|
@@ -363806,7 +363953,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363806
363953
|
deeplink: target.deeplink
|
|
363807
363954
|
});
|
|
363808
363955
|
liveSession = opened;
|
|
363809
|
-
|
|
363956
|
+
attachMirrorWithRelay(opened.sessionId, target.platform, opened.deviceId);
|
|
363810
363957
|
bridgeDriver = new MobileBridgeDriver({
|
|
363811
363958
|
sessionId: opened.sessionId,
|
|
363812
363959
|
platform: target.platform,
|
|
@@ -363996,7 +364143,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363996
364143
|
// Stream this run to its OWN per-device dashboard tile (keyed by the
|
|
363997
364144
|
// leased device id) so N parallel Appium runs each show a live mirror.
|
|
363998
364145
|
onSessionReady: (sessionId, deviceId, sessionPlatform) => {
|
|
363999
|
-
|
|
364146
|
+
attachMirrorWithRelay(sessionId, sessionPlatform, deviceId);
|
|
364000
364147
|
requestLiveBrowserHeartbeat(true);
|
|
364001
364148
|
},
|
|
364002
364149
|
onSessionEnding: (deviceId) => {
|
|
@@ -364588,12 +364735,31 @@ async function startRunner(opts) {
|
|
|
364588
364735
|
const publishLiveFrame = (frame) => {
|
|
364589
364736
|
if (!frame.projectId) return;
|
|
364590
364737
|
if (liveFramePushInFlight.has(frame.sessionId)) return;
|
|
364738
|
+
if (!isLiveFrameWatched(frame.projectId)) {
|
|
364739
|
+
const lastProbeAt = liveFrameIdleProbeAt.get(frame.sessionId) ?? 0;
|
|
364740
|
+
if (frame.capturedAt - lastProbeAt < LIVE_FRAME_IDLE_PUSH_INTERVAL_MS) return;
|
|
364741
|
+
if (liveFrameIdleProbeAt.size >= LIVE_FRAME_IDLE_PROBE_MAX_ENTRIES) {
|
|
364742
|
+
const expiredBefore = frame.capturedAt - LIVE_FRAME_IDLE_PUSH_INTERVAL_MS;
|
|
364743
|
+
for (const [sessionId, probedAt] of liveFrameIdleProbeAt) {
|
|
364744
|
+
if (probedAt >= expiredBefore) break;
|
|
364745
|
+
liveFrameIdleProbeAt.delete(sessionId);
|
|
364746
|
+
}
|
|
364747
|
+
}
|
|
364748
|
+
liveFrameIdleProbeAt.set(frame.sessionId, frame.capturedAt);
|
|
364749
|
+
}
|
|
364591
364750
|
liveFramePushInFlight.add(frame.sessionId);
|
|
364751
|
+
const frameProjectId = frame.projectId;
|
|
364592
364752
|
void fetch(`${serverUrl}/api/runner/live-frame`, {
|
|
364593
364753
|
method: "POST",
|
|
364594
364754
|
headers,
|
|
364595
364755
|
body: JSON.stringify(frame),
|
|
364596
364756
|
signal: AbortSignal.timeout(5e3)
|
|
364757
|
+
}).then(async (res) => {
|
|
364758
|
+
if (!res.ok) return;
|
|
364759
|
+
const ack = await res.json().catch(() => null);
|
|
364760
|
+
if (ack && typeof ack.watching === "boolean") {
|
|
364761
|
+
liveFrameWatchedByProject.set(frameProjectId, ack.watching);
|
|
364762
|
+
}
|
|
364597
364763
|
}).catch(() => {
|
|
364598
364764
|
}).finally(() => {
|
|
364599
364765
|
liveFramePushInFlight.delete(frame.sessionId);
|
package/dist/cli.mjs
CHANGED
|
@@ -1912,7 +1912,7 @@ function isValidLiveFrame(frame) {
|
|
|
1912
1912
|
return false;
|
|
1913
1913
|
return true;
|
|
1914
1914
|
}
|
|
1915
|
-
var readEnvInt, LIVE_FRAME_PUSH_MAX_BASE64_BYTES, LIVE_THUMBNAIL_MAX_BASE64_BYTES, LIVE_FRAME_MAX_DIMENSION, LIVE_FRAME_JPEG_QUALITY, LIVE_FRAME_MIN_PUSH_INTERVAL_MS, LIVE_FRAME_UNFOCUSED_INTERVAL_MS, LIVE_FRAME_STALE_MS, LIVE_FRAME_MAX_SESSIONS_PER_PROJECT, MOBILE_MJPEG_SETTINGS, MOBILE_MJPEG_FIRST_FRAME_TIMEOUT_MS, MOBILE_MJPEG_MAX_FRAME_BYTES;
|
|
1915
|
+
var readEnvInt, LIVE_FRAME_PUSH_MAX_BASE64_BYTES, LIVE_THUMBNAIL_MAX_BASE64_BYTES, LIVE_FRAME_MAX_DIMENSION, LIVE_FRAME_JPEG_QUALITY, LIVE_FRAME_MIN_PUSH_INTERVAL_MS, LIVE_FRAME_UNFOCUSED_INTERVAL_MS, LIVE_FRAME_IDLE_PUSH_INTERVAL_MS, LIVE_FRAME_STALE_MS, LIVE_FRAME_MAX_SESSIONS_PER_PROJECT, MOBILE_MJPEG_SETTINGS, MOBILE_MJPEG_FIRST_FRAME_TIMEOUT_MS, MOBILE_MJPEG_MAX_FRAME_BYTES;
|
|
1916
1916
|
var init_live_frame = __esm({
|
|
1917
1917
|
"../shared/dist/live-frame.js"() {
|
|
1918
1918
|
"use strict";
|
|
@@ -1928,6 +1928,7 @@ var init_live_frame = __esm({
|
|
|
1928
1928
|
LIVE_FRAME_JPEG_QUALITY = readEnvInt("LIVE_FRAME_JPEG_QUALITY", 55);
|
|
1929
1929
|
LIVE_FRAME_MIN_PUSH_INTERVAL_MS = readEnvInt("LIVE_FRAME_MIN_PUSH_INTERVAL_MS", 125);
|
|
1930
1930
|
LIVE_FRAME_UNFOCUSED_INTERVAL_MS = readEnvInt("LIVE_FRAME_UNFOCUSED_INTERVAL_MS", 2e3);
|
|
1931
|
+
LIVE_FRAME_IDLE_PUSH_INTERVAL_MS = readEnvInt("LIVE_FRAME_IDLE_PUSH_INTERVAL_MS", 5e3);
|
|
1931
1932
|
LIVE_FRAME_STALE_MS = readEnvInt("LIVE_FRAME_STALE_MS", 3e4);
|
|
1932
1933
|
LIVE_FRAME_MAX_SESSIONS_PER_PROJECT = readEnvInt("LIVE_FRAME_MAX_SESSIONS_PER_PROJECT", 32);
|
|
1933
1934
|
MOBILE_MJPEG_SETTINGS = {
|
|
@@ -6365,6 +6366,7 @@ __export(dist_exports, {
|
|
|
6365
6366
|
GROUP_LABELS: () => GROUP_LABELS,
|
|
6366
6367
|
LEGACY_TTS_MODEL_MAP: () => LEGACY_TTS_MODEL_MAP,
|
|
6367
6368
|
LEGACY_TTS_VOICE_ID_MAP: () => LEGACY_TTS_VOICE_ID_MAP,
|
|
6369
|
+
LIVE_FRAME_IDLE_PUSH_INTERVAL_MS: () => LIVE_FRAME_IDLE_PUSH_INTERVAL_MS,
|
|
6368
6370
|
LIVE_FRAME_JPEG_QUALITY: () => LIVE_FRAME_JPEG_QUALITY,
|
|
6369
6371
|
LIVE_FRAME_MAX_DIMENSION: () => LIVE_FRAME_MAX_DIMENSION,
|
|
6370
6372
|
LIVE_FRAME_MAX_SESSIONS_PER_PROJECT: () => LIVE_FRAME_MAX_SESSIONS_PER_PROJECT,
|
|
@@ -321056,6 +321058,7 @@ var require_primary_action_ledger = __commonJS({
|
|
|
321056
321058
|
exports.isLedgerActive = isLedgerActive;
|
|
321057
321059
|
exports.isGateEnforcing = isGateEnforcing;
|
|
321058
321060
|
exports.isSteeringActive = isSteeringActive;
|
|
321061
|
+
exports.isPrimaryActionCoverageGapEnabled = isPrimaryActionCoverageGapEnabled;
|
|
321059
321062
|
exports.leadingVerb = leadingVerb;
|
|
321060
321063
|
exports.classifyPrimaryAction = classifyPrimaryAction;
|
|
321061
321064
|
exports.containerScopeOf = containerScopeOf;
|
|
@@ -321070,6 +321073,8 @@ var require_primary_action_ledger = __commonJS({
|
|
|
321070
321073
|
exports.classifyActionOutcome = classifyActionOutcome;
|
|
321071
321074
|
exports.detectPrimaryActions = detectPrimaryActions;
|
|
321072
321075
|
exports.summarizeLedger = summarizeLedger;
|
|
321076
|
+
exports.summarizeUnexercisedPrimaries = summarizeUnexercisedPrimaries;
|
|
321077
|
+
exports.buildUnexercisedPrimaryCoverageGaps = buildUnexercisedPrimaryCoverageGaps;
|
|
321073
321078
|
var reveal_diff_js_1 = require_reveal_diff();
|
|
321074
321079
|
var exploration_parser_js_1 = require_exploration_parser();
|
|
321075
321080
|
function getPrimaryActionGateMode(env = process.env) {
|
|
@@ -321089,6 +321094,10 @@ var require_primary_action_ledger = __commonJS({
|
|
|
321089
321094
|
function isSteeringActive(mode) {
|
|
321090
321095
|
return mode === "advisory" || mode === "enforce";
|
|
321091
321096
|
}
|
|
321097
|
+
function isPrimaryActionCoverageGapEnabled(env = process.env) {
|
|
321098
|
+
const raw = (env.DISCOVERY_PRIMARY_ACTION_COVERAGE_GAP ?? "").trim().toLowerCase();
|
|
321099
|
+
return raw === "1" || raw === "true" || raw === "on" || raw === "enabled";
|
|
321100
|
+
}
|
|
321092
321101
|
exports.PRIMARY_ACTION_VERB_RE = /^(run|analyze|analyse|generate|regenerate|process|submit|save|create|send|upload|search|export|import|calculate|recalculate|compute|convert|start|scan|translate|transcribe|render|publish|sync|deploy|enrich|summar(?:ize|ise))\b/i;
|
|
321093
321102
|
exports.AMBIGUOUS_ACTION_VERB_RE = /^(continue|next|apply|go|proceed|confirm|update|add|edit|ok|done|finish|complete|approve|enable|activate)\b/i;
|
|
321094
321103
|
exports.DESTRUCTIVE_ACTION_VERB_RE = /^(delete|remove|archive|reset|disconnect|deactivate|destroy|purge|revoke|discard|drop|wipe|erase|unsubscribe|cancel|clear|trash)\b/i;
|
|
@@ -321352,6 +321361,47 @@ var require_primary_action_ledger = __commonJS({
|
|
|
321352
321361
|
}
|
|
321353
321362
|
return `actions=${c.total} primary{detected=${c.detected} attempted=${c.attempted} captured=${c.captured} blocked=${c.blocked} exempt=${c.exempt}} ambiguous=${c.ambiguous}`;
|
|
321354
321363
|
}
|
|
321364
|
+
function summarizeUnexercisedPrimaries(ledger, routeInScope, cap = 12) {
|
|
321365
|
+
const out = [];
|
|
321366
|
+
for (const e of getBlockingActionDebt(ledger, routeInScope)) {
|
|
321367
|
+
if (e.status !== "DETECTED" && e.status !== "ATTEMPTED")
|
|
321368
|
+
continue;
|
|
321369
|
+
out.push({ label: e.label, route: e.route, containerScope: e.containerScope, status: e.status });
|
|
321370
|
+
if (out.length >= cap)
|
|
321371
|
+
break;
|
|
321372
|
+
}
|
|
321373
|
+
return out;
|
|
321374
|
+
}
|
|
321375
|
+
var REGEXP_META_RE = /[.*+?^${}()|[\]\\]/g;
|
|
321376
|
+
function labelCoveredByPlan(normLabel, plannedLower) {
|
|
321377
|
+
if (!normLabel.includes(" "))
|
|
321378
|
+
return false;
|
|
321379
|
+
const escaped = normLabel.replace(REGEXP_META_RE, "\\$&");
|
|
321380
|
+
return new RegExp(`(^|\\W)${escaped}($|\\W)`).test(plannedLower);
|
|
321381
|
+
}
|
|
321382
|
+
function buildUnexercisedPrimaryCoverageGaps(unexercised, plannedScenarioText, nowIso) {
|
|
321383
|
+
const gaps = [];
|
|
321384
|
+
const seen = /* @__PURE__ */ new Set();
|
|
321385
|
+
const planned = plannedScenarioText.toLowerCase();
|
|
321386
|
+
for (const a of unexercised) {
|
|
321387
|
+
const norm = normLabelKey(a.label);
|
|
321388
|
+
const key = `${norm}|${a.route}`;
|
|
321389
|
+
if (seen.has(key))
|
|
321390
|
+
continue;
|
|
321391
|
+
seen.add(key);
|
|
321392
|
+
if (labelCoveredByPlan(norm, planned))
|
|
321393
|
+
continue;
|
|
321394
|
+
const clickedNote = a.status === "ATTEMPTED" ? "clicked, but no result state was captured" : "never clicked";
|
|
321395
|
+
gaps.push({
|
|
321396
|
+
scenarioName: `Exercise "${a.label}"`,
|
|
321397
|
+
reason: `Primary action "${a.label}" was detected on ${a.route} but never exercised to an observed result (${clickedNote}).`,
|
|
321398
|
+
suggestion: `Exercise "${a.label}" to a captured result \u2014 open its result modal/panel and capture it BEFORE closing \u2014 or schedule a deep run scoped to this action, so a real test can be authored.`,
|
|
321399
|
+
unmetPrerequisites: [],
|
|
321400
|
+
detectedAt: nowIso
|
|
321401
|
+
});
|
|
321402
|
+
}
|
|
321403
|
+
return gaps;
|
|
321404
|
+
}
|
|
321355
321405
|
}
|
|
321356
321406
|
});
|
|
321357
321407
|
|
|
@@ -322840,6 +322890,9 @@ var require_exploration_prompt_builder = __commonJS({
|
|
|
322840
322890
|
"These are the feature's core actions \u2014 what it EXISTS to do. You are on a PROVISIONED TEST ACCOUNT, so exercising them is the point of this exploration. For each: click it, wait for it to finish (an async job can take 10-60s \u2014 keep waiting and capturing, do not give up early), then capture_page the result (a new panel, row, score, status, or route change).",
|
|
322841
322891
|
`Spending in-app COMPUTE credits is expected and budgeted \u2014 running an Analyze / Generate / Run / Process ONCE to observe its output is exactly what you should do; an "N of M used" usage or AI-credit meter is NOT a reason to skip. These were auto-screened, but YOU are the final check: if you can see a specific listed action actually charges REAL money at a checkout (Buy / Upgrade / Subscribe), makes an irreversible or destructive change (Delete / Wipe / Deploy to production), or sends a message to real people (Send email / Invite), SKIP just that one \u2014 note it as blocked with the reason and move on. Don't finish until every other action below has produced an observed outcome:`
|
|
322842
322892
|
];
|
|
322893
|
+
if ((0, primary_action_ledger_js_1.isPrimaryActionCoverageGapEnabled)()) {
|
|
322894
|
+
out.push("If the result opens in a modal, dialog, or panel, call capture_page WHILE IT IS STILL OPEN \u2014 do not close or dismiss it first, or the result state is never recorded and the action does not count as covered. An action reachable only from inside this feature's own result modal is in-scope: exercise it to a captured result before closing, even if it generates content that belongs to another builder.");
|
|
322895
|
+
}
|
|
322843
322896
|
for (const e of debt.slice(0, 8)) {
|
|
322844
322897
|
const note = e.status === "ATTEMPTED" ? " \u2014 clicked but no result captured yet" : "";
|
|
322845
322898
|
out.push(` - "${e.label}" on ${e.route}${note}`);
|
|
@@ -326660,6 +326713,7 @@ ${inbox_tools_js_1.EMAIL_VERIFICATION_UNAVAILABLE_OVERRIDE_BLOCK}` : ""}`;
|
|
|
326660
326713
|
const primaryActionMode = (0, primary_action_ledger_js_1.getPrimaryActionGateMode)();
|
|
326661
326714
|
const primaryActionLedgerActive = (0, primary_action_ledger_js_1.isLedgerActive)(primaryActionMode) && options?.mode === "deep";
|
|
326662
326715
|
const primaryActionEnforcing = primaryActionLedgerActive && (0, primary_action_ledger_js_1.isGateEnforcing)(primaryActionMode);
|
|
326716
|
+
const primaryActionCoverageGapEnabled = primaryActionLedgerActive && (0, primary_action_ledger_js_1.isPrimaryActionCoverageGapEnabled)();
|
|
326663
326717
|
const prevRouteSignatures = /* @__PURE__ */ new Map();
|
|
326664
326718
|
const prevInteractionCountsByRoute = /* @__PURE__ */ new Map();
|
|
326665
326719
|
let previousCaptureRoute = null;
|
|
@@ -326670,6 +326724,16 @@ ${inbox_tools_js_1.EMAIL_VERIFICATION_UNAVAILABLE_OVERRIDE_BLOCK}` : ""}`;
|
|
|
326670
326724
|
let finishBounce = (0, survey_link_frontier_js_1.initialFinishBounceState)();
|
|
326671
326725
|
let lastLandedPageUrl = null;
|
|
326672
326726
|
const state2 = (0, exploration_state_js_1.createExplorationState)();
|
|
326727
|
+
const buildUnexercisedPrimarySummary = () => {
|
|
326728
|
+
if (!primaryActionCoverageGapEnabled)
|
|
326729
|
+
return void 0;
|
|
326730
|
+
const fa = options?.focusAreas ?? [];
|
|
326731
|
+
if (fa.length === 0)
|
|
326732
|
+
return void 0;
|
|
326733
|
+
const otherRoutes = options?.featureContext?.otherFeatures?.flatMap((f) => f.pages);
|
|
326734
|
+
const summary2 = (0, primary_action_ledger_js_1.summarizeUnexercisedPrimaries)(state2.actionLedger, (r) => (0, exploration_parser_js_1.routeIsWithinFocusAreas)(r, fa, otherRoutes), 12);
|
|
326735
|
+
return summary2.length > 0 ? summary2 : void 0;
|
|
326736
|
+
};
|
|
326673
326737
|
function ingestLinksFromText(rawText) {
|
|
326674
326738
|
if (!linkFrontierEnabled || !rawText || !rawText.includes("- /url:"))
|
|
326675
326739
|
return;
|
|
@@ -328900,6 +328964,7 @@ Exploration ended after ${iteration} iterations without calling finish_explorati
|
|
|
328900
328964
|
summary: state2.summary || summary,
|
|
328901
328965
|
collectedPages: state2.collectedPages,
|
|
328902
328966
|
collectedTransitions: state2.collectedTransitions,
|
|
328967
|
+
unexercisedPrimaryActions: buildUnexercisedPrimarySummary(),
|
|
328903
328968
|
iterationTimeline: iterationTimeline.length > 0 ? iterationTimeline : void 0
|
|
328904
328969
|
};
|
|
328905
328970
|
}
|
|
@@ -328994,6 +329059,7 @@ Fatal error: ${msg}`);
|
|
|
328994
329059
|
summary: `Fatal error: ${msg}`,
|
|
328995
329060
|
collectedPages: state2.collectedPages,
|
|
328996
329061
|
collectedTransitions: state2.collectedTransitions,
|
|
329062
|
+
unexercisedPrimaryActions: buildUnexercisedPrimarySummary(),
|
|
328997
329063
|
iterationTimeline: iterationTimeline.length > 0 ? iterationTimeline : void 0,
|
|
328998
329064
|
truncated: true,
|
|
328999
329065
|
truncatedReason,
|
|
@@ -337180,6 +337246,7 @@ var require_discover_agent = __commonJS({
|
|
|
337180
337246
|
var discover_planner_js_1 = require_discover_planner();
|
|
337181
337247
|
var discover_generator_js_1 = require_discover_generator();
|
|
337182
337248
|
var discover_ai_planner_js_1 = require_discover_ai_planner();
|
|
337249
|
+
var primary_action_ledger_js_1 = require_primary_action_ledger();
|
|
337183
337250
|
var discovery_lint_flags_js_1 = require_discovery_lint_flags();
|
|
337184
337251
|
var verification_inbox_js_1 = require_verification_inbox();
|
|
337185
337252
|
var inbox_tools_js_1 = require_inbox_tools();
|
|
@@ -338482,6 +338549,16 @@ Plan: ${planResult.scenarios.length} scenarios (budget: ${featureTestBudget})`);
|
|
|
338482
338549
|
checkpointFallbackReason = planResult.fallbackReason;
|
|
338483
338550
|
plannerWarning = planResult.warning;
|
|
338484
338551
|
plannerCoverageGaps = planResult.coverageGaps;
|
|
338552
|
+
const unexercisedPrimaries = exploreResult.unexercisedPrimaryActions ?? [];
|
|
338553
|
+
if (unexercisedPrimaries.length > 0) {
|
|
338554
|
+
const plannedText = planResult.scenarios.map((s) => `${s.name} ${s.goal ?? ""} ${(s.targetPages ?? []).join(" ")}`).join(" ");
|
|
338555
|
+
const ledgerGaps = (0, primary_action_ledger_js_1.buildUnexercisedPrimaryCoverageGaps)(unexercisedPrimaries, plannedText, (/* @__PURE__ */ new Date()).toISOString());
|
|
338556
|
+
if (ledgerGaps.length > 0) {
|
|
338557
|
+
plannerCoverageGaps = [...plannerCoverageGaps ?? [], ...ledgerGaps];
|
|
338558
|
+
}
|
|
338559
|
+
const suppressed = unexercisedPrimaries.length - ledgerGaps.length;
|
|
338560
|
+
logs2.push(` [primary-action-ledger] ${unexercisedPrimaries.length} in-scope primary action(s) un-exercised; surfaced ${ledgerGaps.length} as coverage gap(s)${suppressed > 0 ? ` (${suppressed} already covered by the plan / deduped)` : ""}`);
|
|
338561
|
+
}
|
|
338485
338562
|
}
|
|
338486
338563
|
if (callbackOptions?.onPlanReady) {
|
|
338487
338564
|
try {
|
|
@@ -353265,7 +353342,7 @@ var require_package4 = __commonJS({
|
|
|
353265
353342
|
"package.json"(exports, module) {
|
|
353266
353343
|
module.exports = {
|
|
353267
353344
|
name: "@validate.qa/runner",
|
|
353268
|
-
version: "1.0.
|
|
353345
|
+
version: "1.0.81",
|
|
353269
353346
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
353270
353347
|
bin: {
|
|
353271
353348
|
"validate-runner": "dist/cli.js"
|
|
@@ -353534,6 +353611,9 @@ async function enforceRunnerUpToDate(currentVersion, opts = {}) {
|
|
|
353534
353611
|
return result;
|
|
353535
353612
|
}
|
|
353536
353613
|
|
|
353614
|
+
// src/runner.ts
|
|
353615
|
+
init_dist();
|
|
353616
|
+
|
|
353537
353617
|
// src/services/appium-executor.ts
|
|
353538
353618
|
init_dist();
|
|
353539
353619
|
|
|
@@ -358898,6 +358978,7 @@ function startMjpegStream(options) {
|
|
|
358898
358978
|
// src/services/mobile-bridge.ts
|
|
358899
358979
|
init_dist();
|
|
358900
358980
|
var MOBILE_BRIDGE_PORT2 = 9515;
|
|
358981
|
+
var RELAY_DEMAND_POLL_MS = 2e3;
|
|
358901
358982
|
var MAX_REQUEST_BODY_BYTES = 64 * 1024;
|
|
358902
358983
|
var MAX_TEXT_INPUT_LENGTH = 4e3;
|
|
358903
358984
|
var MAX_APP_ID_LENGTH = 255;
|
|
@@ -359409,6 +359490,18 @@ async function handleSessionStop(_req, res) {
|
|
|
359409
359490
|
sendJSON(res, 200, { ok: true });
|
|
359410
359491
|
}
|
|
359411
359492
|
var runnerChannels = /* @__PURE__ */ new Map();
|
|
359493
|
+
var relayDemandTimer = null;
|
|
359494
|
+
function setMirrorRelay(deviceId, relay) {
|
|
359495
|
+
const ch = runnerChannels.get(deviceId);
|
|
359496
|
+
if (!ch) return;
|
|
359497
|
+
ch.relay = relay;
|
|
359498
|
+
if (relay) {
|
|
359499
|
+
ensureRelayDemandSupervisor();
|
|
359500
|
+
if (hasChannelDemand(ch)) ensureChannelPolling(ch);
|
|
359501
|
+
} else {
|
|
359502
|
+
stopChannelCaptureIfIdle(ch);
|
|
359503
|
+
}
|
|
359504
|
+
}
|
|
359412
359505
|
function readDeviceIdParam(req) {
|
|
359413
359506
|
try {
|
|
359414
359507
|
const url = new URL(req.url || "", "http://localhost");
|
|
@@ -359440,7 +359533,7 @@ function writeFrameToChannelClient(ch, client, frame) {
|
|
|
359440
359533
|
} catch {
|
|
359441
359534
|
}
|
|
359442
359535
|
ch.clients.delete(client);
|
|
359443
|
-
|
|
359536
|
+
stopChannelCaptureIfIdle(ch);
|
|
359444
359537
|
return;
|
|
359445
359538
|
}
|
|
359446
359539
|
client.write(`data: ${JSON.stringify(frame)}
|
|
@@ -359452,6 +359545,12 @@ function writeFrameToChannelClient(ch, client, frame) {
|
|
|
359452
359545
|
function broadcastChannelFrame(ch, frame) {
|
|
359453
359546
|
ch.lastFrame = frame;
|
|
359454
359547
|
for (const client of ch.clients) writeFrameToChannelClient(ch, client, frame);
|
|
359548
|
+
if (ch.relay && frame.screenshot) {
|
|
359549
|
+
try {
|
|
359550
|
+
ch.relay.onFrame(frame.screenshot, frame.mimeType ?? "image/png");
|
|
359551
|
+
} catch {
|
|
359552
|
+
}
|
|
359553
|
+
}
|
|
359455
359554
|
}
|
|
359456
359555
|
function tryStartChannelMjpeg(ch) {
|
|
359457
359556
|
if (ch.mjpeg || ch.mjpegStarting || ch.mjpegUnavailable) return;
|
|
@@ -359489,7 +359588,7 @@ function tryStartChannelMjpeg(ch) {
|
|
|
359489
359588
|
ch.mjpeg = null;
|
|
359490
359589
|
ch.mjpegStarting = false;
|
|
359491
359590
|
ch.mjpegUnavailable = true;
|
|
359492
|
-
if (wasActive && ch
|
|
359591
|
+
if (wasActive && hasChannelDemand(ch)) ensureChannelPolling(ch);
|
|
359493
359592
|
}
|
|
359494
359593
|
});
|
|
359495
359594
|
const firstFrameDeadline = setTimeout(() => {
|
|
@@ -359497,12 +359596,30 @@ function tryStartChannelMjpeg(ch) {
|
|
|
359497
359596
|
ch.mjpegStarting = false;
|
|
359498
359597
|
ch.mjpegUnavailable = true;
|
|
359499
359598
|
handle.stop();
|
|
359500
|
-
if (ch
|
|
359599
|
+
if (hasChannelDemand(ch)) ensureChannelPolling(ch);
|
|
359501
359600
|
}, MOBILE_MJPEG_FIRST_FRAME_TIMEOUT_MS);
|
|
359502
359601
|
firstFrameDeadline.unref?.();
|
|
359503
359602
|
}
|
|
359603
|
+
function hasChannelDemand(ch) {
|
|
359604
|
+
if (ch.clients.size > 0) return true;
|
|
359605
|
+
return ch.relay !== null;
|
|
359606
|
+
}
|
|
359607
|
+
function ensureRelayDemandSupervisor() {
|
|
359608
|
+
if (relayDemandTimer) return;
|
|
359609
|
+
relayDemandTimer = setInterval(() => {
|
|
359610
|
+
for (const ch of [...runnerChannels.values()]) {
|
|
359611
|
+
if (hasChannelDemand(ch)) ensureChannelPolling(ch);
|
|
359612
|
+
else stopChannelCaptureIfIdle(ch);
|
|
359613
|
+
}
|
|
359614
|
+
if (runnerChannels.size === 0 && relayDemandTimer) {
|
|
359615
|
+
clearInterval(relayDemandTimer);
|
|
359616
|
+
relayDemandTimer = null;
|
|
359617
|
+
}
|
|
359618
|
+
}, RELAY_DEMAND_POLL_MS);
|
|
359619
|
+
relayDemandTimer.unref?.();
|
|
359620
|
+
}
|
|
359504
359621
|
async function pollRunnerChannel(ch) {
|
|
359505
|
-
if (ch
|
|
359622
|
+
if (!hasChannelDemand(ch) || ch.polling) return;
|
|
359506
359623
|
if (ch.mjpeg) return;
|
|
359507
359624
|
ch.polling = true;
|
|
359508
359625
|
try {
|
|
@@ -359533,6 +359650,7 @@ async function pollRunnerChannel(ch) {
|
|
|
359533
359650
|
}
|
|
359534
359651
|
}
|
|
359535
359652
|
function ensureChannelPolling(ch) {
|
|
359653
|
+
if (!hasChannelDemand(ch)) return;
|
|
359536
359654
|
tryStartChannelMjpeg(ch);
|
|
359537
359655
|
if (ch.mjpeg || ch.pollTimer) return;
|
|
359538
359656
|
ch.pollTimer = setInterval(() => {
|
|
@@ -359540,8 +359658,8 @@ function ensureChannelPolling(ch) {
|
|
|
359540
359658
|
}, MOBILE_SCREENSHOT_INTERVAL_MS);
|
|
359541
359659
|
ch.pollTimer.unref?.();
|
|
359542
359660
|
}
|
|
359543
|
-
function
|
|
359544
|
-
if (ch
|
|
359661
|
+
function stopChannelCaptureIfIdle(ch) {
|
|
359662
|
+
if (hasChannelDemand(ch)) return;
|
|
359545
359663
|
if (ch.pollTimer) {
|
|
359546
359664
|
clearInterval(ch.pollTimer);
|
|
359547
359665
|
ch.pollTimer = null;
|
|
@@ -359560,6 +359678,7 @@ function closeRunnerChannel(ch) {
|
|
|
359560
359678
|
ch.mjpeg.stop();
|
|
359561
359679
|
ch.mjpeg = null;
|
|
359562
359680
|
}
|
|
359681
|
+
ch.relay = null;
|
|
359563
359682
|
for (const client of ch.clients) {
|
|
359564
359683
|
try {
|
|
359565
359684
|
client.end();
|
|
@@ -359568,7 +359687,7 @@ function closeRunnerChannel(ch) {
|
|
|
359568
359687
|
}
|
|
359569
359688
|
ch.clients.clear();
|
|
359570
359689
|
ch.lastFrame = null;
|
|
359571
|
-
runnerChannels.delete(ch.deviceId);
|
|
359690
|
+
if (runnerChannels.get(ch.deviceId) === ch) runnerChannels.delete(ch.deviceId);
|
|
359572
359691
|
}
|
|
359573
359692
|
function closeAllRunnerChannels() {
|
|
359574
359693
|
for (const ch of [...runnerChannels.values()]) closeRunnerChannel(ch);
|
|
@@ -359590,7 +359709,7 @@ async function serveRunnerMirror(req, res, ch) {
|
|
|
359590
359709
|
}
|
|
359591
359710
|
req.on("close", () => {
|
|
359592
359711
|
ch.clients.delete(res);
|
|
359593
|
-
|
|
359712
|
+
stopChannelCaptureIfIdle(ch);
|
|
359594
359713
|
});
|
|
359595
359714
|
}
|
|
359596
359715
|
async function handleMirror(req, res) {
|
|
@@ -360148,7 +360267,8 @@ function attachMirrorSession(sessionId, platform3, deviceId) {
|
|
|
360148
360267
|
mjpeg: null,
|
|
360149
360268
|
mjpegLastFrameAt: 0,
|
|
360150
360269
|
mjpegStarting: false,
|
|
360151
|
-
mjpegUnavailable: false
|
|
360270
|
+
mjpegUnavailable: false,
|
|
360271
|
+
relay: null
|
|
360152
360272
|
};
|
|
360153
360273
|
ch.sessionId = sessionId;
|
|
360154
360274
|
ch.platform = platform3;
|
|
@@ -360162,7 +360282,7 @@ function attachMirrorSession(sessionId, platform3, deviceId) {
|
|
|
360162
360282
|
runnerChannels.set(deviceId, ch);
|
|
360163
360283
|
void applyMjpegSettings(`/session/${sessionId}`, MOBILE_MJPEG_SETTINGS).catch(() => {
|
|
360164
360284
|
});
|
|
360165
|
-
if (ch
|
|
360285
|
+
if (hasChannelDemand(ch)) {
|
|
360166
360286
|
ensureChannelPolling(ch);
|
|
360167
360287
|
void pollRunnerChannel(ch);
|
|
360168
360288
|
}
|
|
@@ -361134,6 +361254,13 @@ function attachMobileDiscoveryNetworkContext(calls, result, appId) {
|
|
|
361134
361254
|
}));
|
|
361135
361255
|
}
|
|
361136
361256
|
var runnerPackage = require_package4();
|
|
361257
|
+
var MIRROR_RELAY_FRESH_MS = 3e3;
|
|
361258
|
+
var liveFrameWatchedByProject = /* @__PURE__ */ new Map();
|
|
361259
|
+
var liveFrameIdleProbeAt = /* @__PURE__ */ new Map();
|
|
361260
|
+
var LIVE_FRAME_IDLE_PROBE_MAX_ENTRIES = 256;
|
|
361261
|
+
function isLiveFrameWatched(projectId) {
|
|
361262
|
+
return !projectId || liveFrameWatchedByProject.get(projectId) !== false;
|
|
361263
|
+
}
|
|
361137
361264
|
var SECRET_VAR_PATTERN = /password|secret|token|api[_]?key|pass\b|credential|bearer|pat\b/i;
|
|
361138
361265
|
var IS_CLOUD = parseBoolFlag(process.env.RUNNER_CLOUD);
|
|
361139
361266
|
var RUNNER_NAME = runnerPackage.name ?? "@validate.qa/runner";
|
|
@@ -361911,6 +362038,22 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
361911
362038
|
testCaseId: run2.testCaseId ?? void 0
|
|
361912
362039
|
});
|
|
361913
362040
|
requestLiveBrowserHeartbeat(true);
|
|
362041
|
+
let lastMirrorRelayFrameAt = 0;
|
|
362042
|
+
const attachMirrorWithRelay = (sessionId, platform3, deviceId) => {
|
|
362043
|
+
attachMirrorSession(sessionId, platform3, deviceId);
|
|
362044
|
+
if (!deviceId) return;
|
|
362045
|
+
setMirrorRelay(deviceId, {
|
|
362046
|
+
onFrame: (base64, mimeType) => {
|
|
362047
|
+
lastMirrorRelayFrameAt = Date.now();
|
|
362048
|
+
liveBrowserHandle.updateSnapshot({
|
|
362049
|
+
thumbnailJpegBase64: base64,
|
|
362050
|
+
thumbnailCapturedAt: Date.now(),
|
|
362051
|
+
mimeType
|
|
362052
|
+
});
|
|
362053
|
+
},
|
|
362054
|
+
wanted: () => isLiveFrameWatched(run2.projectId)
|
|
362055
|
+
});
|
|
362056
|
+
};
|
|
361914
362057
|
const liveBrowserHeartbeatInterval = setInterval(() => {
|
|
361915
362058
|
liveBrowserHandle.patch({});
|
|
361916
362059
|
requestLiveBrowserHeartbeat();
|
|
@@ -362814,7 +362957,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
362814
362957
|
deeplink: harvestTarget.deeplink
|
|
362815
362958
|
},
|
|
362816
362959
|
onSessionReady: (sessionId, deviceId, sessionPlatform) => {
|
|
362817
|
-
|
|
362960
|
+
attachMirrorWithRelay(sessionId, sessionPlatform, deviceId);
|
|
362818
362961
|
const name = getCachedDeviceSnapshot().find((d) => d.id === deviceId)?.name;
|
|
362819
362962
|
liveBrowserHandle.patch({ deviceId, ...name ? { deviceName: name } : {} });
|
|
362820
362963
|
requestLiveBrowserHeartbeat(true);
|
|
@@ -363284,7 +363427,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363284
363427
|
}
|
|
363285
363428
|
);
|
|
363286
363429
|
mobileOnLog(`Appium session created: ${session.sessionId} on device ${session.deviceId}`);
|
|
363287
|
-
|
|
363430
|
+
attachMirrorWithRelay(session.sessionId, platform3, session.deviceId);
|
|
363288
363431
|
const driver = new MobileBridgeDriver({
|
|
363289
363432
|
sessionId: session.sessionId,
|
|
363290
363433
|
platform: platform3,
|
|
@@ -363321,7 +363464,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363321
363464
|
throw new Error("discovery teardown started during session recovery \u2014 replacement session closed");
|
|
363322
363465
|
}
|
|
363323
363466
|
session = replacement;
|
|
363324
|
-
|
|
363467
|
+
attachMirrorWithRelay(replacement.sessionId, platform3, replacement.deviceId);
|
|
363325
363468
|
liveBrowserHandle.patch({
|
|
363326
363469
|
deviceId: replacement.deviceId,
|
|
363327
363470
|
deviceName: replacement.deviceName
|
|
@@ -363342,6 +363485,10 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363342
363485
|
// UI a reliable mobile fallback when the direct bridge SSE is
|
|
363343
363486
|
// still opening or blocked by the user's network/tunnel.
|
|
363344
363487
|
onScreenshot: (base64) => {
|
|
363488
|
+
if (Date.now() - lastMirrorRelayFrameAt < MIRROR_RELAY_FRESH_MS) {
|
|
363489
|
+
liveBrowserHandle.updateSnapshot({ currentUrl: mobileTarget.appId });
|
|
363490
|
+
return;
|
|
363491
|
+
}
|
|
363345
363492
|
liveBrowserHandle.updateSnapshot({
|
|
363346
363493
|
currentUrl: mobileTarget.appId,
|
|
363347
363494
|
thumbnailJpegBase64: base64,
|
|
@@ -363814,7 +363961,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363814
363961
|
deeplink: target.deeplink
|
|
363815
363962
|
});
|
|
363816
363963
|
liveSession = opened;
|
|
363817
|
-
|
|
363964
|
+
attachMirrorWithRelay(opened.sessionId, target.platform, opened.deviceId);
|
|
363818
363965
|
bridgeDriver = new MobileBridgeDriver({
|
|
363819
363966
|
sessionId: opened.sessionId,
|
|
363820
363967
|
platform: target.platform,
|
|
@@ -364004,7 +364151,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
364004
364151
|
// Stream this run to its OWN per-device dashboard tile (keyed by the
|
|
364005
364152
|
// leased device id) so N parallel Appium runs each show a live mirror.
|
|
364006
364153
|
onSessionReady: (sessionId, deviceId, sessionPlatform) => {
|
|
364007
|
-
|
|
364154
|
+
attachMirrorWithRelay(sessionId, sessionPlatform, deviceId);
|
|
364008
364155
|
requestLiveBrowserHeartbeat(true);
|
|
364009
364156
|
},
|
|
364010
364157
|
onSessionEnding: (deviceId) => {
|
|
@@ -364596,12 +364743,31 @@ async function startRunner(opts) {
|
|
|
364596
364743
|
const publishLiveFrame = (frame) => {
|
|
364597
364744
|
if (!frame.projectId) return;
|
|
364598
364745
|
if (liveFramePushInFlight.has(frame.sessionId)) return;
|
|
364746
|
+
if (!isLiveFrameWatched(frame.projectId)) {
|
|
364747
|
+
const lastProbeAt = liveFrameIdleProbeAt.get(frame.sessionId) ?? 0;
|
|
364748
|
+
if (frame.capturedAt - lastProbeAt < LIVE_FRAME_IDLE_PUSH_INTERVAL_MS) return;
|
|
364749
|
+
if (liveFrameIdleProbeAt.size >= LIVE_FRAME_IDLE_PROBE_MAX_ENTRIES) {
|
|
364750
|
+
const expiredBefore = frame.capturedAt - LIVE_FRAME_IDLE_PUSH_INTERVAL_MS;
|
|
364751
|
+
for (const [sessionId, probedAt] of liveFrameIdleProbeAt) {
|
|
364752
|
+
if (probedAt >= expiredBefore) break;
|
|
364753
|
+
liveFrameIdleProbeAt.delete(sessionId);
|
|
364754
|
+
}
|
|
364755
|
+
}
|
|
364756
|
+
liveFrameIdleProbeAt.set(frame.sessionId, frame.capturedAt);
|
|
364757
|
+
}
|
|
364599
364758
|
liveFramePushInFlight.add(frame.sessionId);
|
|
364759
|
+
const frameProjectId = frame.projectId;
|
|
364600
364760
|
void fetch(`${serverUrl}/api/runner/live-frame`, {
|
|
364601
364761
|
method: "POST",
|
|
364602
364762
|
headers,
|
|
364603
364763
|
body: JSON.stringify(frame),
|
|
364604
364764
|
signal: AbortSignal.timeout(5e3)
|
|
364765
|
+
}).then(async (res) => {
|
|
364766
|
+
if (!res.ok) return;
|
|
364767
|
+
const ack = await res.json().catch(() => null);
|
|
364768
|
+
if (ack && typeof ack.watching === "boolean") {
|
|
364769
|
+
liveFrameWatchedByProject.set(frameProjectId, ack.watching);
|
|
364770
|
+
}
|
|
364605
364771
|
}).catch(() => {
|
|
364606
364772
|
}).finally(() => {
|
|
364607
364773
|
liveFramePushInFlight.delete(frame.sessionId);
|
package/package.json
CHANGED