@loadmill/droid-cua 2.29.0 → 2.30.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/README.md +4 -4
- package/build/index.js +142 -10
- package/build/src/browser/cloud-browser-catalog.js +27 -0
- package/build/src/browser/factory.js +33 -10
- package/build/src/browser/gesture-dispatch.js +281 -0
- package/build/src/browser/interface.js +3 -0
- package/build/src/browser/normalize.js +3 -1
- package/build/src/browser/playwright/actions.js +5 -271
- package/build/src/browser/webdriver/actions.js +10 -0
- package/build/src/browser/webdriver/client.js +83 -0
- package/build/src/browser/webdriver/connection.js +300 -0
- package/build/src/browser/webdriver/page-facade.js +123 -0
- package/build/src/browser/webdriver/session-state.js +35 -0
- package/build/src/cli/headless-execution-config.js +1 -1
- package/build/src/cli/headless-runner.js +26 -13
- package/build/src/cli/headless-target.js +87 -16
- package/build/src/cli/headless-test-source-label.js +4 -0
- package/build/src/cli/loadmill-cloud-device-connect-summary.js +6 -0
- package/build/src/cli/loadmill-cloud-session.js +26 -0
- package/build/src/core/context-optimization.js +1 -1
- package/build/src/core/execution-engine.js +9 -0
- package/build/src/core/runtime-config.js +2 -2
- package/build/src/desktop-shared/connection-predicates.js +52 -0
- package/build/src/device/cloud/loadmill-cloud/adapter.js +17 -12
- package/build/src/device/cloud/loadmill-cloud/artifact-downloader.js +13 -7
- package/build/src/device/cloud/loadmill-cloud/control-plane-client.js +22 -5
- package/build/src/device/cloud/loadmill-cloud/errors.js +25 -0
- package/build/src/device/cloud/loadmill-cloud/run-pool.js +41 -0
- package/build/src/device/cloud/loadmill-cloud/run-status.js +4 -0
- package/package.json +1 -1
- package/skill/droid-cua/SKILL.md +10 -3
- package/skill/droid-cua/agents/openai.yaml +2 -2
- package/skill/droid-cua/references/authoring-tests.md +36 -6
- package/skill/droid-cua/references/iterative-improvement.md +143 -0
- package/skill/droid-cua/references/onboarding-and-troubleshooting.md +19 -0
- package/skill/droid-cua/references/project-and-auth.md +2 -0
- package/skill/droid-cua/references/running-and-healing.md +32 -14
|
@@ -4,13 +4,9 @@ import { reportCloudNote, reportCloudPhase, reportCloudPhaseCloseText, reportClo
|
|
|
4
4
|
import { LoadmillCloudControlPlaneClient } from "./control-plane-client.js";
|
|
5
5
|
import { downloadArtifacts } from "./artifact-downloader.js";
|
|
6
6
|
import { formatBytes } from "./format-bytes.js";
|
|
7
|
+
import { ARTIFACT_READY_STATUSES, TERMINAL_STATUSES } from "./run-status.js";
|
|
7
8
|
import { timingLog } from "./timing.js";
|
|
8
9
|
const RESUMABLE_STATUSES = new Set(["PROVISIONED", "CONNECTED", "RUNNING"]);
|
|
9
|
-
const TERMINAL_STATUSES = new Set(["COMPLETED", "CANCELLED", "ERROR"]);
|
|
10
|
-
// Artifacts are readable from DF while a run is still tearing down
|
|
11
|
-
// (STOPPING), not just once it reaches a true terminal state. Mirrors the
|
|
12
|
-
// server's ARTIFACT_READY_STATES.
|
|
13
|
-
const ARTIFACT_READY_STATUSES = new Set(["STOPPING", ...TERMINAL_STATUSES]);
|
|
14
10
|
const SLOT_HELD_STATUSES = new Set(["PREPARING", "PROVISIONED", "CONNECTED", "RUNNING"]);
|
|
15
11
|
const ARTIFACT_POLL_BUDGET_MS = 3 * 60_000;
|
|
16
12
|
const ARTIFACT_POLL_INTERVAL_MS = 5_000;
|
|
@@ -25,7 +21,11 @@ function deviceCacheKey(creds) {
|
|
|
25
21
|
}
|
|
26
22
|
let activeRun = null;
|
|
27
23
|
function makeClient(creds) {
|
|
28
|
-
return new LoadmillCloudControlPlaneClient({
|
|
24
|
+
return new LoadmillCloudControlPlaneClient({
|
|
25
|
+
bearerToken: creds.bearerToken,
|
|
26
|
+
baseUrl: creds.baseUrl,
|
|
27
|
+
pool: "mobile",
|
|
28
|
+
});
|
|
29
29
|
}
|
|
30
30
|
function mapDevice(device) {
|
|
31
31
|
return {
|
|
@@ -189,11 +189,9 @@ async function pollForArtifacts(client, sessionId, kinds, { budgetMs, intervalMs
|
|
|
189
189
|
}
|
|
190
190
|
catch (error) {
|
|
191
191
|
const retryable = (error?.status === 409 && error?.code === "artifacts_not_ready") ||
|
|
192
|
-
(error?.status
|
|
192
|
+
(error?.status >= 500 && error?.status < 600);
|
|
193
193
|
if (retryable) {
|
|
194
|
-
timingLog(error.status
|
|
195
|
-
? `poll #${pollNumber}: 409 artifacts_not_ready`
|
|
196
|
-
: `poll #${pollNumber}: 502 mobile_testing_artifacts_failed`);
|
|
194
|
+
timingLog(`poll #${pollNumber}: ${error.status} ${error.code ?? "unknown"}`);
|
|
197
195
|
}
|
|
198
196
|
else {
|
|
199
197
|
throw error;
|
|
@@ -218,7 +216,11 @@ function formatDownloadDetail(fileName, bytesWritten, totalBytes) {
|
|
|
218
216
|
}
|
|
219
217
|
return `downloading ${fileName} — ${formatBytes(bytesWritten)}`;
|
|
220
218
|
}
|
|
221
|
-
|
|
219
|
+
export function formatArtifactRetryCommand({ client, sessionId, kinds }) {
|
|
220
|
+
const targetFlag = client?.pool === "web" ? "--target web " : "";
|
|
221
|
+
return `droid-cua ${targetFlag}--cloud-session-id ${sessionId} --artifacts ${kinds.join(",")}`;
|
|
222
|
+
}
|
|
223
|
+
export async function fetchAndDownloadArtifacts({ client, sessionId, kinds }) {
|
|
222
224
|
reportCloudPhase("artifact list");
|
|
223
225
|
const { artifacts, missingKinds, elapsedMs } = await pollForArtifacts(client, sessionId, kinds, {
|
|
224
226
|
budgetMs: ARTIFACT_POLL_BUDGET_MS,
|
|
@@ -256,11 +258,14 @@ async function fetchAndDownloadArtifacts({ client, sessionId, kinds }) {
|
|
|
256
258
|
reportCloudPrint(`Artifacts saved to ${destDir}`);
|
|
257
259
|
}
|
|
258
260
|
if (missingKinds.length > 0) {
|
|
259
|
-
reportCloudNote(`WARNING: artifacts not ready for: ${missingKinds.join(", ")}. Retry later with:
|
|
261
|
+
reportCloudNote(`WARNING: artifacts not ready for: ${missingKinds.join(", ")}. Retry later with: ${formatArtifactRetryCommand({ client, sessionId, kinds })}`);
|
|
260
262
|
}
|
|
261
263
|
return { downloadedKinds: summary.map((item) => item.kind), missingKinds };
|
|
262
264
|
}
|
|
263
265
|
async function uploadAppIfNeeded(client, sessionId, { app, platform, signal }) {
|
|
266
|
+
if (!app) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
264
269
|
const fileName = path.basename(app);
|
|
265
270
|
const sha256 = await hashFile(app);
|
|
266
271
|
reportCloudPhase("register app");
|
|
@@ -4,9 +4,9 @@ import { mkdir } from "node:fs/promises";
|
|
|
4
4
|
import { Readable, Transform } from "node:stream";
|
|
5
5
|
import { pipeline } from "node:stream/promises";
|
|
6
6
|
import { isTimingEnabled, timingLog } from "./timing.js";
|
|
7
|
-
// Mirrors the server-side caps: per-artifact 1 GiB (video/deviceLog/appiumLog),
|
|
7
|
+
// Mirrors the server-side caps: per-artifact 1 GiB (video/deviceLog/appiumLog/seleniumLog),
|
|
8
8
|
// 4 GiB cumulative across a single download batch.
|
|
9
|
-
export const ARTIFACT_KINDS = ["video", "deviceLog", "appiumLog"];
|
|
9
|
+
export const ARTIFACT_KINDS = ["video", "deviceLog", "appiumLog", "seleniumLog"];
|
|
10
10
|
export const PER_ARTIFACT_CAP_BYTES = 1 * 1024 * 1024 * 1024;
|
|
11
11
|
export const CUMULATIVE_CAP_BYTES = 4 * 1024 * 1024 * 1024;
|
|
12
12
|
export const DOWNLOAD_TIMEOUT_MS = 10 * 60 * 1000;
|
|
@@ -14,6 +14,7 @@ const KIND_DEFAULT_FILENAMES = {
|
|
|
14
14
|
video: "video.mp4",
|
|
15
15
|
deviceLog: "device.log",
|
|
16
16
|
appiumLog: "appium.log",
|
|
17
|
+
seleniumLog: "selenium.log",
|
|
17
18
|
};
|
|
18
19
|
function normalizeKinds(kinds) {
|
|
19
20
|
if (kinds == null || kinds === "all") {
|
|
@@ -24,8 +25,11 @@ function normalizeKinds(kinds) {
|
|
|
24
25
|
}
|
|
25
26
|
return [kinds];
|
|
26
27
|
}
|
|
27
|
-
function deriveFileName(kind, url) {
|
|
28
|
+
function deriveFileName(kind, url, indexForKind = 0) {
|
|
28
29
|
const defaultName = KIND_DEFAULT_FILENAMES[kind] || `${kind}.bin`;
|
|
30
|
+
const suffix = indexForKind > 0 ? `-${indexForKind + 1}` : "";
|
|
31
|
+
const stemOf = (name) => (name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : name);
|
|
32
|
+
const extOf = (name) => (name.includes(".") ? name.slice(name.lastIndexOf(".")) : "");
|
|
29
33
|
try {
|
|
30
34
|
const parsed = new URL(url);
|
|
31
35
|
const base = path.basename(parsed.pathname);
|
|
@@ -33,14 +37,13 @@ function deriveFileName(kind, url) {
|
|
|
33
37
|
// Allows extensions like ".logcat" (6 chars) — DF's device-log artifact
|
|
34
38
|
// URLs use that extension and must not be forced to the ".log" default.
|
|
35
39
|
if (/^\.[a-zA-Z0-9]{1,8}$/.test(ext)) {
|
|
36
|
-
|
|
37
|
-
return `${stem}${ext}`;
|
|
40
|
+
return `${stemOf(defaultName)}${suffix}${ext}`;
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
catch {
|
|
41
44
|
// Not a parseable URL — fall back to the default name below.
|
|
42
45
|
}
|
|
43
|
-
return defaultName
|
|
46
|
+
return `${stemOf(defaultName)}${suffix}${extOf(defaultName)}`;
|
|
44
47
|
}
|
|
45
48
|
// Truncates the stream in-place at `cap` bytes: chunks past the cap are
|
|
46
49
|
// dropped (not pushed downstream), so the write stream stops growing while
|
|
@@ -118,7 +121,10 @@ perArtifactCapBytes = PER_ARTIFACT_CAP_BYTES, cumulativeCapBytes = CUMULATIVE_CA
|
|
|
118
121
|
await mkdir(destDir, { recursive: true });
|
|
119
122
|
const summary = [];
|
|
120
123
|
let cumulativeBytes = 0;
|
|
124
|
+
const kindIndexCounters = new Map();
|
|
121
125
|
for (const artifact of filtered) {
|
|
126
|
+
const indexForKind = kindIndexCounters.get(artifact.kind) ?? 0;
|
|
127
|
+
kindIndexCounters.set(artifact.kind, indexForKind + 1);
|
|
122
128
|
if (cumulativeBytes >= cumulativeCapBytes) {
|
|
123
129
|
onNote?.(`WARNING: ${artifact.kind} skipped — cumulative artifact cap (${cumulativeCapBytes} bytes) reached`);
|
|
124
130
|
continue;
|
|
@@ -127,7 +133,7 @@ perArtifactCapBytes = PER_ARTIFACT_CAP_BYTES, cumulativeCapBytes = CUMULATIVE_CA
|
|
|
127
133
|
if (typeof artifact.bytes === "number" && artifact.bytes > perArtifactCap) {
|
|
128
134
|
onNote?.(`WARNING: ${artifact.kind} is ${artifact.bytes} bytes, exceeds the ${perArtifactCap} byte cap and will be truncated`);
|
|
129
135
|
}
|
|
130
|
-
const fileName = deriveFileName(artifact.kind, artifact.presignedGetUrl);
|
|
136
|
+
const fileName = deriveFileName(artifact.kind, artifact.presignedGetUrl, indexForKind);
|
|
131
137
|
const filePath = path.join(destDir, fileName);
|
|
132
138
|
const downloadStartedAt = Date.now();
|
|
133
139
|
timingLog(`download start: kind=${artifact.kind} url=${artifact.presignedGetUrl}`);
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
const DEFAULT_BASE_URL = "https://app.loadmill.com";
|
|
4
|
-
const
|
|
4
|
+
const POOL_API_PATHS = {
|
|
5
|
+
mobile: "/mobile-testing-cloud/v1",
|
|
6
|
+
web: "/web-testing-cloud/v1",
|
|
7
|
+
};
|
|
8
|
+
const DEFAULT_POOL = "mobile";
|
|
5
9
|
const LOADMILL_CLIENT_HEADER_VALUE = "cli";
|
|
6
10
|
const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
7
11
|
// The binary PUT carries the actual app file (tens to low-hundreds of MB) and needs a
|
|
@@ -13,7 +17,14 @@ const POLL_FALLBACK_INTERVAL_MS = 3_000;
|
|
|
13
17
|
const SSE_RETRY_INTERVAL_MS = 5_000;
|
|
14
18
|
// Server sends a `: ping` keepalive every 25s; treat total silence this long as a wedged connection.
|
|
15
19
|
const SSE_IDLE_TIMEOUT_MS = 60_000;
|
|
16
|
-
|
|
20
|
+
export function apiPathForPool(pool) {
|
|
21
|
+
const apiPath = POOL_API_PATHS[pool];
|
|
22
|
+
if (!apiPath) {
|
|
23
|
+
throw new Error(`Unknown Loadmill Cloud pool: ${pool}. Expected "mobile" or "web".`);
|
|
24
|
+
}
|
|
25
|
+
return apiPath;
|
|
26
|
+
}
|
|
27
|
+
// Each pool's control plane mounts at its own top-level path, not under /api — do not append /api.
|
|
17
28
|
function normalizeApiBaseUrl(rawBaseUrl) {
|
|
18
29
|
const candidate = (rawBaseUrl || DEFAULT_BASE_URL).trim();
|
|
19
30
|
if (!candidate) {
|
|
@@ -67,20 +78,23 @@ async function buildResponseError(response) {
|
|
|
67
78
|
}
|
|
68
79
|
export class LoadmillCloudControlPlaneClient {
|
|
69
80
|
/**
|
|
70
|
-
* @param {{ bearerToken: string, baseUrl?: string, requestTimeoutMs?: number, uploadTimeoutMs?: number }} [options]
|
|
81
|
+
* @param {{ bearerToken: string, baseUrl?: string, pool?: "mobile" | "web", apiPath?: string, requestTimeoutMs?: number, uploadTimeoutMs?: number }} [options]
|
|
71
82
|
*/
|
|
72
|
-
constructor({ bearerToken, baseUrl, requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS, uploadTimeoutMs = DEFAULT_UPLOAD_TIMEOUT_MS, } = {}) {
|
|
83
|
+
constructor({ bearerToken, baseUrl, pool = DEFAULT_POOL, apiPath, requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS, uploadTimeoutMs = DEFAULT_UPLOAD_TIMEOUT_MS, } = {}) {
|
|
73
84
|
if (!bearerToken || typeof bearerToken !== "string" || !bearerToken.trim()) {
|
|
74
85
|
throw new Error("LoadmillCloudControlPlaneClient requires a bearerToken.");
|
|
75
86
|
}
|
|
87
|
+
const poolApiPath = apiPathForPool(pool);
|
|
76
88
|
this.bearerToken = bearerToken.trim();
|
|
77
89
|
this.baseUrl = normalizeApiBaseUrl(baseUrl);
|
|
90
|
+
this.pool = pool;
|
|
91
|
+
this.apiPath = apiPath ?? poolApiPath;
|
|
78
92
|
this.requestTimeoutMs = requestTimeoutMs;
|
|
79
93
|
this.uploadTimeoutMs = uploadTimeoutMs;
|
|
80
94
|
this._heartbeatTimer = null;
|
|
81
95
|
}
|
|
82
96
|
_url(pathSuffix) {
|
|
83
|
-
return `${this.baseUrl}${
|
|
97
|
+
return `${this.baseUrl}${this.apiPath}${pathSuffix}`;
|
|
84
98
|
}
|
|
85
99
|
_authHeaders(extra = {}) {
|
|
86
100
|
return {
|
|
@@ -136,6 +150,9 @@ export class LoadmillCloudControlPlaneClient {
|
|
|
136
150
|
});
|
|
137
151
|
}
|
|
138
152
|
async registerApp(sessionId, { sha256, platform, fileName }) {
|
|
153
|
+
if (this.pool === "web") {
|
|
154
|
+
throw new Error("Loadmill Cloud web runs do not register an app — the web control plane has no /runs/:sessionId/app route.");
|
|
155
|
+
}
|
|
139
156
|
return this._request(`/runs/${encodeURIComponent(sessionId)}/app`, {
|
|
140
157
|
method: "POST",
|
|
141
158
|
body: { sha256, platform, fileName },
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
export const LOADMILL_CLOUD_USAGE_LIMIT_ERROR_CODES = new Set([
|
|
2
2
|
"mobile_testing_day_minute_quota_exceeded",
|
|
3
3
|
"mobile_testing_week_minute_quota_exceeded",
|
|
4
|
+
"mobile_testing_month_minute_quota_exceeded",
|
|
4
5
|
"mobile_testing_fixed_budget_minute_quota_exceeded",
|
|
5
6
|
"mobile_testing_concurrency_exceeded",
|
|
6
7
|
"mobile_testing_rate_limit_exceeded",
|
|
8
|
+
"loadmill_cloud_web_day_minute_quota_exceeded",
|
|
9
|
+
"loadmill_cloud_web_week_minute_quota_exceeded",
|
|
10
|
+
"loadmill_cloud_web_month_minute_quota_exceeded",
|
|
11
|
+
"loadmill_cloud_web_fixed_budget_minute_quota_exceeded",
|
|
12
|
+
"loadmill_cloud_web_concurrency_exceeded",
|
|
13
|
+
"loadmill_cloud_web_rate_limit_exceeded",
|
|
7
14
|
]);
|
|
15
|
+
export const LOADMILL_CLOUD_NOT_ENTITLED_ERROR_CODES = new Set([
|
|
16
|
+
"mobile_testing_cloud_not_allowed",
|
|
17
|
+
"loadmill_cloud_web_not_allowed",
|
|
18
|
+
]);
|
|
19
|
+
const LOADMILL_CLOUD_NOT_ENTITLED_PRODUCT_NAMES = {
|
|
20
|
+
mobile_testing_cloud_not_allowed: "Loadmill Cloud mobile testing",
|
|
21
|
+
loadmill_cloud_web_not_allowed: "Loadmill Cloud web testing",
|
|
22
|
+
};
|
|
8
23
|
export function isLoadmillCloudUsageLimitError(err) {
|
|
9
24
|
if (!err || typeof err !== "object") {
|
|
10
25
|
return false;
|
|
@@ -14,3 +29,13 @@ export function isLoadmillCloudUsageLimitError(err) {
|
|
|
14
29
|
}
|
|
15
30
|
return err.status === 429 && err.type === "rate_limit_error";
|
|
16
31
|
}
|
|
32
|
+
export function isLoadmillCloudNotEntitledError(err) {
|
|
33
|
+
if (!err || typeof err !== "object") {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return typeof err.code === "string" && LOADMILL_CLOUD_NOT_ENTITLED_ERROR_CODES.has(err.code);
|
|
37
|
+
}
|
|
38
|
+
export function formatLoadmillCloudNotEntitledMessage(err) {
|
|
39
|
+
const product = LOADMILL_CLOUD_NOT_ENTITLED_PRODUCT_NAMES[err?.code] ?? "Loadmill Cloud";
|
|
40
|
+
return `Your team is not entitled to ${product}. Contact Loadmill support to enable it.`;
|
|
41
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { LoadmillCloudControlPlaneClient } from "./control-plane-client.js";
|
|
2
|
+
import { isLoadmillCloudNotEntitledError } from "./errors.js";
|
|
3
|
+
export const LOADMILL_CLOUD_POOLS = ["web", "mobile"];
|
|
4
|
+
function isOtherPoolResponse(error) {
|
|
5
|
+
return error?.status === 404 || isLoadmillCloudNotEntitledError(error);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Resolves which pool a Loadmill Cloud session belongs to by asking each pool's
|
|
9
|
+
* router for the run: the server 404s a run-scoped call whose pool does not
|
|
10
|
+
* match the mount, and the run state itself carries no pool field. A pool the
|
|
11
|
+
* team is not entitled to answers 403 before the run is ever looked up, which
|
|
12
|
+
* is equally a "not this pool" answer.
|
|
13
|
+
*
|
|
14
|
+
* @param {{ credentials: { bearerToken: string, baseUrl?: string }, sessionId: string }} args
|
|
15
|
+
* @returns {Promise<{ pool: "mobile" | "web", client: LoadmillCloudControlPlaneClient, state: object }>}
|
|
16
|
+
*/
|
|
17
|
+
export async function resolveRunPool({ credentials, sessionId }) {
|
|
18
|
+
let firstDenial = null;
|
|
19
|
+
for (const pool of LOADMILL_CLOUD_POOLS) {
|
|
20
|
+
const client = new LoadmillCloudControlPlaneClient({
|
|
21
|
+
bearerToken: credentials.bearerToken,
|
|
22
|
+
baseUrl: credentials.baseUrl,
|
|
23
|
+
pool,
|
|
24
|
+
});
|
|
25
|
+
try {
|
|
26
|
+
return { pool, client, state: await client.getRunState(sessionId) };
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (!isOtherPoolResponse(error)) {
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
if (!firstDenial && isLoadmillCloudNotEntitledError(error)) {
|
|
33
|
+
firstDenial = error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (firstDenial) {
|
|
38
|
+
throw firstDenial;
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`Loadmill Cloud session ${sessionId} was not found in either the web or the mobile pool.`);
|
|
41
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const TERMINAL_STATUSES = new Set(["COMPLETED", "CANCELLED", "ERROR"]);
|
|
2
|
+
// Artifacts are readable while a run is still tearing down (STOPPING), not just
|
|
3
|
+
// once it reaches a true terminal state. Mirrors the server's ARTIFACT_READY_STATES.
|
|
4
|
+
export const ARTIFACT_READY_STATUSES = new Set(["STOPPING", ...TERMINAL_STATUSES]);
|
package/package.json
CHANGED
package/skill/droid-cua/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: droid-cua
|
|
3
|
-
description: Operate Droid CUA `.dcua` test projects. Use when the user explicitly names Droid CUA or @loadmill/droid-cua, or asks to author, configure, run, or
|
|
3
|
+
description: Operate and improve Droid CUA `.dcua` test projects. Use when the user explicitly names Droid CUA or @loadmill/droid-cua, or asks to author, configure, run, diagnose, heal, stabilize, or speed up `.dcua` tests with the Droid CUA CLI or desktop app. Do not use for general web browsing, website development, browser automation, generic mobile work, or non-Droid test tasks.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Droid CUA
|
|
@@ -15,7 +15,8 @@ Use the existing `droid-cua` CLI and repository files. Do not invent commands, f
|
|
|
15
15
|
4. Author or update the smallest useful test. Read [authoring-tests.md](references/authoring-tests.md) before changing `.dcua`, context, test data, secrets, or config.
|
|
16
16
|
5. Before execution, present the proposed tests and target to the user, resolve any questions, and get their confirmation. Read [running-and-healing.md](references/running-and-healing.md) for the required pre-run check.
|
|
17
17
|
6. Run the confirmed, narrowest relevant test and monitor it to completion. Keep the user oriented during the run by surfacing meaningful live screenshots, not just terminal output.
|
|
18
|
-
7.
|
|
18
|
+
7. For a new, changed, flaky, slow, or explicitly optimized test, continue with the evidence-driven author-run-improve loop in [iterative-improvement.md](references/iterative-improvement.md). A passing run is a baseline, not proof of readiness.
|
|
19
|
+
8. End with a short user-facing improvement summary: what was tested, the main problems found, what changed and why, measured timing or stability gains, final readiness, and any remaining risk. Include evidence paths separately without making the user reconstruct the story from reports or the full run history.
|
|
19
20
|
|
|
20
21
|
## Safety
|
|
21
22
|
|
|
@@ -27,7 +28,10 @@ Use the existing `droid-cua` CLI and repository files. Do not invent commands, f
|
|
|
27
28
|
- During setup, do not create or edit user tests. Rerun only the failed readiness check, then resume the original task.
|
|
28
29
|
- During healing, edit only `.dcua`, `context.md`, `test-data.md`, or Droid CUA config. Never modify application code under the guise of healing a test.
|
|
29
30
|
- Never weaken or remove an assertion merely to make a failing run pass.
|
|
30
|
-
-
|
|
31
|
+
- Keep one canonical context file and one canonical Droid CUA config file per test project. Reuse and update those shared files; never create test-specific context or config copies for a new or improved test.
|
|
32
|
+
- When improving an existing test, keep its complete flow in one independently runnable `.dcua` file. Do not split it into ordered or state-dependent tests as an optimization; Droid CUA does not currently guarantee that separate tests will run consecutively or share state.
|
|
33
|
+
- Before rerunning a long test, apply the smallest evidence-supported batch that addresses its known bottlenecks. The batch may include multiple `.dcua`, context, test-data, or config improvements when each has a clear rationale and preserves coverage. Record the complete change set; isolate or revert individual changes only if the combined result regresses or remains ambiguous.
|
|
34
|
+
- When the user asks to create, improve, heal, stabilize, or optimize a test, continue the evidence-driven edit/rerun loop through a final representative run until the test is ready or a genuine blocker remains. Do not stop because an arbitrary number of reruns has been reached.
|
|
31
35
|
- Stop immediately when evidence indicates a likely application regression; do not rewrite the test to match broken behavior.
|
|
32
36
|
- Gracefully allow cloud runs to finish cleanup. Avoid terminating a metered device session unless necessary.
|
|
33
37
|
|
|
@@ -35,5 +39,8 @@ Use the existing `droid-cua` CLI and repository files. Do not invent commands, f
|
|
|
35
39
|
|
|
36
40
|
- Prefer an existing project convention over creating a parallel structure.
|
|
37
41
|
- Prefer headless runs for agent work because they are observable and return deterministic exit codes.
|
|
42
|
+
- Write tests as concise, human-readable user journeys. Prefer one business-level instruction per user goal over one instruction per tap, menu, dialog, or other UI gesture; add lower-level guidance only when run evidence shows it is necessary.
|
|
38
43
|
- Use explicit, user-observable outcomes in tests rather than implementation details or coordinates.
|
|
39
44
|
- Diagnose the first divergence between the instruction and observed behavior before changing anything.
|
|
45
|
+
- Compare runs on the same target and app build, and clearly identify every test, context, data, model, or config change included in the optimization batch.
|
|
46
|
+
- Treat internal recovery loops, repeated screenshots, previews, compactions, and retries as stability signals even when the run passes.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
interface:
|
|
2
|
-
display_name: "Droid
|
|
3
|
-
short_description: "
|
|
2
|
+
display_name: "Droid CUA"
|
|
3
|
+
short_description: "Author, run, diagnose, and stabilize .dcua tests"
|
|
4
4
|
icon_small: "./assets/droid-cua-app-icon.png"
|
|
5
5
|
icon_large: "./assets/droid-cua-app-icon.png"
|
|
6
6
|
brand_color: "#1895F7"
|
|
@@ -6,14 +6,15 @@ Find nearby `.dcua` tests and project files before writing. Match their director
|
|
|
6
6
|
|
|
7
7
|
## Write `.dcua` instructions
|
|
8
8
|
|
|
9
|
-
- Put one clear
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- Break
|
|
9
|
+
- Put one clear user goal or assertion on each non-empty line. One instruction does not need to equal one UI action.
|
|
10
|
+
- Write concise, human-readable business or journey instructions that describe what the user wants to accomplish. Prefer `Select the desired date` or `Log out` over separate instructions for opening a picker or menu, clicking controls, handling an expected confirmation, and submitting.
|
|
11
|
+
- Do not prescribe coordinates, selectors, implementation details, model strategy, or every tap and keystroke. These micro-steps couple the test to incidental UI structure, add model rounds, and make harmless UI changes more likely to break it.
|
|
12
|
+
- Break a business-level instruction into smaller actions only when run evidence shows that the agent cannot complete it reliably or the intermediate state is itself behavior the test must verify.
|
|
13
13
|
- State the starting condition when it is not guaranteed by the environment.
|
|
14
14
|
- Use explicit assertions for important outcomes. Prefer stable visible facts such as a screen title, confirmation message, changed value, or navigation result.
|
|
15
15
|
- Use exact visible labels when naming controls or outcomes.
|
|
16
|
-
- Keep each test focused on one behavior.
|
|
16
|
+
- Keep each new test focused on one behavior. Separate unrelated flows only when each resulting test is fully independent and does not rely on execution order or state created by another test.
|
|
17
|
+
- When updating or optimizing an existing test, preserve its complete flow in one `.dcua` file. Do not split it into sequential or dependent tests unless the user explicitly requests that structure and the project has a verified orchestration mechanism that guarantees ordering and shared state.
|
|
17
18
|
- Use `//` comments sparingly for human context; comments are not executed.
|
|
18
19
|
- Never include secret values in the test.
|
|
19
20
|
|
|
@@ -22,18 +23,47 @@ Example:
|
|
|
22
23
|
```text
|
|
23
24
|
Sign in using the USER_EMAIL and USER_PASSWORD secrets.
|
|
24
25
|
Assert that the Home screen is visible and shows the signed-in account.
|
|
26
|
+
Select December 15 as the requested date.
|
|
27
|
+
Assert that December 15 is shown as the requested date.
|
|
28
|
+
Log out.
|
|
29
|
+
Assert that the sign-in screen is visible.
|
|
25
30
|
```
|
|
26
31
|
|
|
32
|
+
Keep meaningful outcomes explicit even when actions are high-level. For example, follow `Log out` with an assertion that the sign-in screen is visible when logout behavior is under test.
|
|
33
|
+
|
|
27
34
|
Use the secret key names that exist in `.secrets`; do not inspect their values. If key discovery is necessary, filter the file so only keys reach output, for example `sed -n 's/^\([^#][^=]*\)=.*/\1/p' .secrets`. If a required key is absent, ask the user to add it without sharing its value.
|
|
28
35
|
|
|
36
|
+
## Invoke a saved Loadmill flow
|
|
37
|
+
|
|
38
|
+
When a test should run an existing Loadmill flow, invoke it either with the explicit `loadmill: <request>` form or with a natural-language instruction that clearly mentions Loadmill, such as `Run the Loadmill <flow name> flow with username=name.` Describe the flow with its exact or distinctive name. If several flows could match, ask the user for identifying terms before writing the test.
|
|
39
|
+
|
|
40
|
+
The request is interpreted as natural language. When passing runtime parameter overrides, prefer `name=value`, because that is the explicitly supported extraction format; include it naturally in the request rather than treating the whole instruction as rigid syntax.
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
loadmill: create premium customer with country=US
|
|
44
|
+
Sign in as the customer created by the Loadmill flow.
|
|
45
|
+
Assert that the Premium dashboard is visible.
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Droid searches for the best matching Loadmill flow, starts it, and waits for it to finish before continuing. On success, relevant returned parameters can be added to the agent context for later instructions. Refer to those values semantically, as in the example; do not invent placeholder syntax such as `{email}` or include returned values in the `.dcua` file.
|
|
49
|
+
|
|
29
50
|
## Put knowledge in the right place
|
|
30
51
|
|
|
31
|
-
- Put durable product behavior and
|
|
52
|
+
- Put durable product behavior, terminology, stable navigation knowledge, and recurring product-specific confirmations in `context.md`, not repeated in every test. Use context to make business intent unambiguous, not to hide a scenario-specific micro-script.
|
|
32
53
|
- Put non-secret reusable people, accounts, products, and scenario combinations in `test-data.md`.
|
|
33
54
|
- Put credentials and sensitive values in `.secrets`.
|
|
34
55
|
- Put reproducible headless settings in `ci/droid-cua.json`.
|
|
35
56
|
- Keep environment and provider credentials in `.env` or the shell/CI secret store.
|
|
36
57
|
|
|
58
|
+
Use one shared context file and one shared Droid CUA config file for the project:
|
|
59
|
+
|
|
60
|
+
- Find the canonical files from nearby tests, existing commands, and config references before creating anything. If they exist, update them in place; do not create files such as `<test-name>-context.md` or `<test-name>-config.json`.
|
|
61
|
+
- Add only durable, project-wide knowledge to the shared context. Keep scenario-specific steps, values, and expected outcomes in the `.dcua` test or test data.
|
|
62
|
+
- Treat the shared config as affecting every project test. Review its existing consumers and likely impact before changing it. Use a CLI override for a temporary experiment, then persist the setting in the canonical config only when it is intended to become the project-wide behavior.
|
|
63
|
+
- If the project has no context or config, create at most one canonical file of each kind using its established directory and naming conventions.
|
|
64
|
+
|
|
37
65
|
Use `--scenario "<goal>" --test-data <path>` only when the user wants generated permutations or cases. A normal `.dcua` run does not automatically consume `test-data.md`.
|
|
38
66
|
|
|
39
67
|
After editing, show the user the test instructions and explain any new context, data keys, or secret keys required. Never show secret values.
|
|
68
|
+
|
|
69
|
+
Do not call a new or materially changed test complete after static validation. Continue with the confirmed baseline, evidence analysis, and stability workflow in [iterative-improvement.md](iterative-improvement.md).
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Iterative test improvement
|
|
2
|
+
|
|
3
|
+
Use this workflow after creating or changing a test, when a test is flaky or slow, or when the user asks to stabilize, heal, or optimize it. Do not wait for a failure: a green run can still contain expensive recovery loops and recurring instability.
|
|
4
|
+
|
|
5
|
+
## 1. Agree on the iterative run
|
|
6
|
+
|
|
7
|
+
Before the first run, describe the test behavior, target, user-visible side effects, and metered-resource use as required by `running-and-healing.md`. When iteration is requested, explain that the workflow includes a baseline followed by the evidence-driven edits, targeted reruns, and final representative runs needed to reach a readiness decision on the same target.
|
|
8
|
+
|
|
9
|
+
One confirmation may cover that iterative workflow. Continue without asking again merely because another rerun is needed. Confirm again before changing the app build, target, test scope, provider/model, or another user-visible side effect.
|
|
10
|
+
|
|
11
|
+
## 2. Establish a reproducible baseline
|
|
12
|
+
|
|
13
|
+
Run the narrowest test with its existing project model and config. Keep these fixed for the baseline, then change any of them only as part of a documented, evidence-supported optimization batch:
|
|
14
|
+
|
|
15
|
+
- App build and initial account/data state.
|
|
16
|
+
- Device, OS, device source, or browser session mode.
|
|
17
|
+
- Test, context, model/provider, Aim Assist, compaction, and Explain Mode settings.
|
|
18
|
+
- Report, debug, and cloud artifact settings.
|
|
19
|
+
|
|
20
|
+
Use `--debug` and an explicit `--report` path. Preserve the project's Explain Mode setting for the performance baseline. When reasoning is unclear, run a separate diagnostic comparison with Explain Mode enabled, then repeat final confidence runs with the intended CI setting.
|
|
21
|
+
|
|
22
|
+
On Loadmill Cloud, use `--artifacts video`, `--artifacts deviceLog,appiumLog`, or `--artifacts all` to download selected artifacts after the run reaches terminal status. `--artifacts` is Loadmill-Cloud-only. Use video when motion or timing matters and device/Appium logs when platform or automation behavior is in question. An existing cloud run can fetch artifacts retroactively with `--cloud-session-id <id> --artifacts <kinds>` and no instructions.
|
|
23
|
+
|
|
24
|
+
## 3. Build an evidence timeline
|
|
25
|
+
|
|
26
|
+
Inspect the HTML report, structured debug JSONL, safe screenshots, terminal output, and available video or platform logs. Separate cloud provisioning and artifact-download time from test execution time.
|
|
27
|
+
|
|
28
|
+
Record at least:
|
|
29
|
+
|
|
30
|
+
- Pass/fail, completed instructions, assertions, framework retries, action count, and total execution time.
|
|
31
|
+
- Per-instruction duration and the first divergence from expected visible behavior.
|
|
32
|
+
- Model rounds, screenshot-only turns, previews versus real actions, repeated coordinates, waits, compactions, and reconnects around each hotspot.
|
|
33
|
+
- In-step recoveries that do not increment the CLI retry counter, such as repeated typing, refocusing, reopening the app, or resubmitting.
|
|
34
|
+
- Whether the UI changed after each action and whether the same anomaly recurs in another run.
|
|
35
|
+
|
|
36
|
+
Rank hotspots by time, excess rounds/actions, and risk. Do not assume the slowest step is an app delay: repeated observe-decide-act cycles often dominate execution time.
|
|
37
|
+
|
|
38
|
+
## 4. Classify before editing
|
|
39
|
+
|
|
40
|
+
Use the first-divergence categories in `running-and-healing.md`, with two additional distinctions:
|
|
41
|
+
|
|
42
|
+
- **Droid/provider behavior**: aim-preview loops, compaction state loss, unsafe automation-tool behavior, model confusion despite clear instructions, or provider/network retries. Do not disguise these as app context.
|
|
43
|
+
- **Performance-only opportunity**: the flow is correct, but an unnecessary scenario step, redundant instruction, repeated action, or overly conservative setting adds time without coverage value.
|
|
44
|
+
|
|
45
|
+
Stop healing when evidence indicates an application regression. Preserve the failing assertion and report the product bug. Retry an unchanged test once for a plausible transient failure before editing.
|
|
46
|
+
|
|
47
|
+
## 5. Choose the smallest useful batch
|
|
48
|
+
|
|
49
|
+
For long-running tests, fix all well-supported bottlenecks that can be addressed safely before paying for another full run. Use this order to build the batch unless evidence points elsewhere:
|
|
50
|
+
|
|
51
|
+
1. **`.dcua` wording and scope**
|
|
52
|
+
- Express one human-readable business goal per instruction, not one click, menu, field, dialog, or submit action per line.
|
|
53
|
+
- Collapse consecutive micro-steps into the user outcome they jointly accomplish, such as `Select the desired date` or `Log out`, while keeping meaningful assertions explicit.
|
|
54
|
+
- Use exact visible labels only when they disambiguate the goal or identify an outcome the test must verify.
|
|
55
|
+
- Add the missing starting condition or assertion at the first ambiguous transition.
|
|
56
|
+
- Remove scenario steps that do not protect the behavior under test.
|
|
57
|
+
- Add lower-level actions only when the evidence shows the business-level instruction is unreliable or an intermediate state is part of the coverage.
|
|
58
|
+
- Keep the existing scenario in one independently runnable `.dcua` file. Splitting a long test into ordered or state-dependent tests is not an available optimization because separate execution order and shared state are not guaranteed.
|
|
59
|
+
- Never remove or weaken a meaningful assertion to obtain a pass.
|
|
60
|
+
|
|
61
|
+
2. **`context.md`**
|
|
62
|
+
- Update the project's existing canonical context file; never create a test-specific context file for the optimization.
|
|
63
|
+
- Add durable product navigation, terminology, stable interruptions, and narrowly observed recovery behavior.
|
|
64
|
+
- Move reusable mechanics out of the test, but keep scenario-specific values and expectations in the test.
|
|
65
|
+
- Remove redundant, speculative, or conflicting instructions. Do not restate Droid's base behavior or encode a workaround for a Droid/product bug as product knowledge.
|
|
66
|
+
|
|
67
|
+
3. **Test data or reproducible config**
|
|
68
|
+
- Use stable, rerunnable data and prevent values from growing or colliding across runs.
|
|
69
|
+
- Use the project's existing canonical config rather than creating a config for the affected test.
|
|
70
|
+
- Evaluate how a setting would affect the other project tests. Use a CLI override for a temporary experiment; persist it in the canonical config only when the evidence supports making it project-wide.
|
|
71
|
+
|
|
72
|
+
4. **Execution configuration**
|
|
73
|
+
- Include only settings supported by the evidence. Keep the prior run as the comparison baseline and record every override.
|
|
74
|
+
|
|
75
|
+
For every included change, record the evidence, rationale, expected effect, and preserved coverage. Bundle multiple `.dcua`, context, test-data, and configuration improvements when they address distinct observed hotspots and do not conflict. Prefer low-risk wording and context fixes; include model or execution-setting changes when evidence supports them and the saved run time outweighs the loss of perfect attribution. After editing, rerun only the affected test on the same target and compare each hotspot plus the overall result against the baseline.
|
|
76
|
+
|
|
77
|
+
## Configuration decision guide
|
|
78
|
+
|
|
79
|
+
### CUA model
|
|
80
|
+
|
|
81
|
+
- Preserve the project's model first. For a project without an established choice, use `loadmill-pulse`; it is the recommended model for daily authoring, local execution, and CI.
|
|
82
|
+
- Use `loadmill-smart` when stability is a real, model-related issue and comparison evidence shows that switching from Pulse improves the affected flow. Do not escalate to Smart based only on a failure, slow app transition, ambiguous test, or target problem.
|
|
83
|
+
- `loadmill-beacon` is opt-in and starts a fresh model conversation for every instruction. Cross-step facts come from Droid's client-owned transcript rather than a scenario-long model chain, so interpret late-run context and compaction differently and verify that cross-step references remain reliable.
|
|
84
|
+
- Treat a model change as a higher-risk part of the batch. Prefer addressing obvious test/context ambiguity first. Keep Smart in the final configuration only when the available evidence supports its stability benefit.
|
|
85
|
+
- Do not silently switch provider or model. Keep provider/model combinations valid.
|
|
86
|
+
|
|
87
|
+
### App context and prompt customizations
|
|
88
|
+
|
|
89
|
+
- Keep app context enabled for normal runs. `context.md` should describe stable product behavior, not the current scenario transcript or model strategy.
|
|
90
|
+
- Use `--no-context` only as a diagnostic comparison when evidence suggests stale or conflicting context; it is not a normal healing step.
|
|
91
|
+
- Treat base/execution prompt customizations as high-blast-radius settings because they affect every instruction. Inspect them for rules that cause repeated screenshots, waits, keyboard actions, or conflicts with Droid's base prompt.
|
|
92
|
+
- Prefer fixing the narrow `.dcua` instruction or `context.md` entry first. Change a shared prompt customization only when the same behavior affects multiple tests and the evidence supports the broader rule.
|
|
93
|
+
|
|
94
|
+
### Aim Assist
|
|
95
|
+
|
|
96
|
+
- `always` is the default and previews every click. It maximizes coordinate verification but adds preview rounds and latency.
|
|
97
|
+
- `smart` may skip preview for broad, isolated, stable targets while retaining it for small, dense, ambiguous, keyboard-adjacent, or recently changed UI. Use it when `always` adds repeated safe previews or material time.
|
|
98
|
+
- `off` clicks directly. Use it only for a controlled comparison or a flow whose targets are consistently large and unambiguous; watch for misclicks and neighboring controls.
|
|
99
|
+
- A preview followed by another screenshot instead of a real click is a diagnostic signal. Check compaction, screen changes, and prompt/context conflicts before merely changing Aim Assist.
|
|
100
|
+
|
|
101
|
+
### Context Optimization
|
|
102
|
+
|
|
103
|
+
- Context Optimization is enabled by default with a 60,000-input-token threshold. The threshold triggers compaction; it is not a guaranteed post-compaction size.
|
|
104
|
+
- Repeated compaction near a late-run hotspot can lose short-lived action state or add latency. Raising the threshold may reduce compaction frequency but retains more history and can increase request size, latency, and usage.
|
|
105
|
+
- A lower threshold reduces the working history sooner but can cause more frequent compaction and loss of relevant state in long flows.
|
|
106
|
+
- Disable optimization only for a short diagnostic comparison when the model/provider supports the accumulated context. Do not make disabling it the default fix for a long test.
|
|
107
|
+
- Prefer removing unrelated steps from the scenario or reducing unnecessary rounds before relying on a very high threshold. Do not turn one existing scenario into dependent test files as a compaction workaround.
|
|
108
|
+
|
|
109
|
+
### Explain Mode and artifacts
|
|
110
|
+
|
|
111
|
+
- Enable Explain Mode for a diagnostic comparison when the baseline does not reveal why the agent acted or stopped. It changes the execution prompt and may add tokens and latency, so do not compare its performance directly with a normal-mode baseline or silently keep it in routine CI.
|
|
112
|
+
- `--debug` is required for agent-observed runs because it provides structured JSONL and instruction-linked screenshots.
|
|
113
|
+
- Use an HTML report for every normal run. Use Loadmill Cloud video for motion/timing ambiguity and device/Appium logs for platform or automation-tool causality.
|
|
114
|
+
|
|
115
|
+
## 6. Compare and iterate
|
|
116
|
+
|
|
117
|
+
After each rerun, compare:
|
|
118
|
+
|
|
119
|
+
- Did the first divergence disappear for the expected reason?
|
|
120
|
+
- Did the hotspot's duration, rounds, actions, previews, retries, or recoveries improve?
|
|
121
|
+
- Did another step regress or did coverage change?
|
|
122
|
+
- Did the combined change set improve the intended hotspots without obscuring a regression?
|
|
123
|
+
|
|
124
|
+
Keep the batch when the evidence supports the overall result. If it regresses or remains ambiguous, use the timeline to isolate, revise, or revert only the suspect subset instead of automatically rerunning every change separately. Continue while the evidence supports another useful test, context, data, or configuration improvement. Stop and report the strongest remaining explanation only when a genuine blocker remains or no evidence-supported improvement is left to try.
|
|
125
|
+
|
|
126
|
+
## 7. Prove readiness
|
|
127
|
+
|
|
128
|
+
After the optimized test passes, decide whether the accumulated evidence is strong enough for the test's risk, prior flakiness, state sensitivity, and intended CI use. A clean representative pass may be sufficient when the behavior is straightforward and earlier iterations already provide consistent evidence. Run the final test unchanged again when meaningful uncertainty remains—for example after a major last edit, intermittent failures, state-dependent behavior, or a configuration change. Prefer a fresh cloud device or equivalently reset local state when repeatability across sessions matters.
|
|
129
|
+
|
|
130
|
+
Call the test ship-ready only when:
|
|
131
|
+
|
|
132
|
+
- All intended instructions and meaningful assertions pass in the final representative evidence.
|
|
133
|
+
- No unexplained recovery, repeated-action loop, or recurring high-risk hotspot remains.
|
|
134
|
+
- Timing and action counts are reasonably consistent, with transient provider retries identified separately.
|
|
135
|
+
- The test is rerunnable and leaves account/app state in the expected condition.
|
|
136
|
+
- Test, context, data, secrets references, and config are reproducible in CI.
|
|
137
|
+
- Reports and debug evidence exist, while secrets and generated artifacts remain uncommitted unless the project explicitly tracks them.
|
|
138
|
+
|
|
139
|
+
Report before/after metrics, the final configuration, the evidence supporting the readiness judgment, remaining product/Droid/provider risks, and exact artifact paths. State why the available run history is sufficient or why more evidence is still needed. A stable test may still reveal a separate platform or product issue; record that risk instead of encoding it into the test.
|
|
140
|
+
|
|
141
|
+
## 8. Summarize the improvement
|
|
142
|
+
|
|
143
|
+
End the work with the concise retrospective required by `running-and-healing.md`. Lead with the final result, then highlight the important problems found, the changes and their rationale, and the measured timing or stability improvement. Distill a long iteration history into the few facts the user needs to understand what became faster or more reliable and why. Separate artifact links and lower-level diagnostics from this summary.
|
|
@@ -4,6 +4,25 @@ Follow the Desktop Device Setup Wizard sequence: choose a platform, run checks,
|
|
|
4
4
|
|
|
5
5
|
Do not create or modify user test files merely to perform setup. Get approval before installing software, changing system configuration, running `sudo`, or accepting a license. After remediation, rerun only the failed check.
|
|
6
6
|
|
|
7
|
+
## Droid CUA CLI compatibility
|
|
8
|
+
|
|
9
|
+
If the CLI rejects a model, option, command, or capability that this skill explicitly documents, treat an outdated global `@loadmill/droid-cua` package as a likely setup issue. Do not suggest an update for an ordinary test, target, provider, or application failure.
|
|
10
|
+
|
|
11
|
+
Check the installed and latest published versions:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm list -g @loadmill/droid-cua --depth=0
|
|
15
|
+
npm view @loadmill/droid-cua version
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
If the global package is missing or behind the published version, explain the mismatch and get approval before updating it:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm install -g @loadmill/droid-cua@latest
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
After the update, rerun only the rejected command or failed readiness check, then resume the original task.
|
|
25
|
+
|
|
7
26
|
## Android
|
|
8
27
|
|
|
9
28
|
Resolve `adb` and `emulator` from `ANDROID_HOME`, `ANDROID_SDK_ROOT`, the macOS default `~/Library/Android/sdk`, the Windows default `%LOCALAPPDATA%\Android\Sdk`, then `PATH`. Respect explicit `DROID_CUA_ANDROID_ADB_PATH` and `DROID_CUA_ANDROID_EMULATOR_PATH` overrides.
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
- `logs/`: structured JSONL and screenshot artifacts created by `--debug`.
|
|
12
12
|
- HTML reports: self-contained evidence produced by `--report`.
|
|
13
13
|
|
|
14
|
+
Treat `context.md` and `ci/droid-cua.json` as canonical project-level resources. Discover and reuse the paths established by the project; do not create one context or config per `.dcua` test. If the project has neither file, create at most one canonical context and one canonical config using the repository's existing layout conventions.
|
|
15
|
+
|
|
14
16
|
Keep `.env` and `.secrets` out of version control. Use `git check-ignore -q .env` and `git check-ignore -q .secrets` to check ignore coverage without opening either file.
|
|
15
17
|
|
|
16
18
|
## Loadmill authentication
|