common-qa-mcp 0.8.0 → 0.8.1
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/adapters/remoteMcp/adapter.js +1 -1
- package/dist/adapters/simplifyqa/adapter.d.ts +24 -0
- package/dist/adapters/simplifyqa/adapter.d.ts.map +1 -1
- package/dist/adapters/simplifyqa/adapter.js +360 -25
- package/dist/adapters/simplifyqa/adapter.js.map +1 -1
- package/dist/adapters/simplifyqa/mapper.d.ts +2 -2
- package/dist/adapters/simplifyqa/mapper.d.ts.map +1 -1
- package/dist/adapters/simplifyqa/mapper.js +257 -64
- package/dist/adapters/simplifyqa/mapper.js.map +1 -1
- package/dist/domain/types.d.ts +31 -4
- package/dist/domain/types.d.ts.map +1 -1
- package/dist/domain/validate.d.ts.map +1 -1
- package/dist/domain/validate.js +5 -1
- package/dist/domain/validate.js.map +1 -1
- package/dist/initAgents.d.ts.map +1 -1
- package/dist/initAgents.js +18 -11
- package/dist/initAgents.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +6 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/run.tools.d.ts +4 -0
- package/dist/tools/run.tools.d.ts.map +1 -1
- package/dist/tools/run.tools.js +200 -105
- package/dist/tools/run.tools.js.map +1 -1
- package/dist/tools/schemas.d.ts +41 -49
- package/dist/tools/schemas.d.ts.map +1 -1
- package/dist/tools/schemas.js +19 -1
- package/dist/tools/schemas.js.map +1 -1
- package/dist/tools/tcEditPublisher.d.ts +20 -0
- package/dist/tools/tcEditPublisher.d.ts.map +1 -0
- package/dist/tools/tcEditPublisher.js +46 -0
- package/dist/tools/tcEditPublisher.js.map +1 -0
- package/dist/tools/testcase.tools.d.ts.map +1 -1
- package/dist/tools/testcase.tools.js +299 -3
- package/dist/tools/testcase.tools.js.map +1 -1
- package/package.json +1 -1
|
@@ -38,7 +38,7 @@ export class RemoteMcpAdapter {
|
|
|
38
38
|
async client() {
|
|
39
39
|
if (!this.clientPromise) {
|
|
40
40
|
this.clientPromise = (async () => {
|
|
41
|
-
const client = new Client({ name: 'common-qa-mcp', version: '0.8.
|
|
41
|
+
const client = new Client({ name: 'common-qa-mcp', version: '0.8.1' });
|
|
42
42
|
// The `as Transport` cast bridges exactOptionalPropertyTypes: StreamableHTTPClientTransport
|
|
43
43
|
// implements Transport, but the SDK's own .d.ts (built without EOPT) leaves its optional
|
|
44
44
|
// callbacks (onclose?/onerror?/onmessage?) without `| undefined`, which this project's EOPT
|
|
@@ -25,6 +25,15 @@ export declare class SimplifyQaAdapter {
|
|
|
25
25
|
private backlogDefaultsCache;
|
|
26
26
|
constructor(cfg: ConnectedQaConfig, transport?: SimplifyQaTransport);
|
|
27
27
|
private headers;
|
|
28
|
+
/** Public session context for MCP viewer/recorder windows that need to authenticate the sqa-ui
|
|
29
|
+
* they load. cfg itself stays private; this getter is the one seam through which tc_edit_open
|
|
30
|
+
* and record tools can forward token + baseUrl + projectId to Electron without asking the LLM. */
|
|
31
|
+
getSessionContext(): {
|
|
32
|
+
token: string;
|
|
33
|
+
baseUrl: string;
|
|
34
|
+
projectId?: string;
|
|
35
|
+
userId?: string;
|
|
36
|
+
};
|
|
28
37
|
private requireProjectId;
|
|
29
38
|
private searchBody;
|
|
30
39
|
/** Validates baseUrl + token + (optional) project by running the same list_projects search the
|
|
@@ -320,6 +329,9 @@ export declare class SimplifyQaAdapter {
|
|
|
320
329
|
* Keyed by `${technologyType}::${lowerCaseName}` so the same action name across two techs
|
|
321
330
|
* (e.g. "click" for WEB + ANDROID) resolves to the right numeric id. */
|
|
322
331
|
private actionCatalogCache;
|
|
332
|
+
/** Populated by actionCatalog() on each fetch. Surfaced in resolveAutomationSteps error messages
|
|
333
|
+
* so a user hitting "no SQA action named X" can see what the fetch actually returned. */
|
|
334
|
+
private actionCatalogFetchDiag;
|
|
323
335
|
private actionCatalog;
|
|
324
336
|
private resolveObjectId;
|
|
325
337
|
/** For every automation step, resolve `actionId` (from action name + technology) and
|
|
@@ -330,6 +342,10 @@ export declare class SimplifyQaAdapter {
|
|
|
330
342
|
* collation strength:2, so a case-sensitive search here misses params stored under different
|
|
331
343
|
* casing — the next POST then trips "Duplicate Name Error". Returns null when not found. */
|
|
332
344
|
private findParameterId;
|
|
345
|
+
/** Case-insensitive project-scoped lookup of an existing object by `disname` (the UI's Name).
|
|
346
|
+
* Lets tc_draft_update pass `objectRef:{name}` for an already-defined object without also
|
|
347
|
+
* supplying locators — mirrors what a user picking from the Object Repository dropdown does. */
|
|
348
|
+
private findObjectIdByName;
|
|
333
349
|
private getOrCreateParameter;
|
|
334
350
|
createSuite(s: Suite): Promise<SavedRef>;
|
|
335
351
|
/** Fetch raw suite, apply add/remove/reorder ops in memory, then PUT the whole doc back with a
|
|
@@ -469,6 +485,14 @@ export declare class SimplifyQaAdapter {
|
|
|
469
485
|
private lookupProjectName;
|
|
470
486
|
startRun(target: RunTarget, opts: RunOptions): Promise<RunRef>;
|
|
471
487
|
stopRun(runId: string): Promise<void>;
|
|
488
|
+
/** SUITE dispatch. Mirrors `startRun` above (which handles TC) — same `/exec/execution` endpoint,
|
|
489
|
+
* same LOCAL-agent fast-fail, same project-name / environment resolution. What differs is on the
|
|
490
|
+
* wire: `type: 'SUITE'`, `executionType` sourced from `suite.suiteType`, `mode` defaulting to
|
|
491
|
+
* `suite.executionType`, a populated `globalConfiguration` block, and one `executionItems` entry
|
|
492
|
+
* PER member — each carrying the suite row's persisted `versionId` / `stepId` / `platform` / `seq`
|
|
493
|
+
* (NOT a re-derived random step id). See sqa-ui execution-popup-modal.component.ts:621-1105 SU
|
|
494
|
+
* branch for the authoritative UI builder; every field below traces back to a line there. */
|
|
495
|
+
private startSuiteRun;
|
|
472
496
|
/** GET the agent /mcp with a 1s timeout. Any HTTP response (incl. 4xx from an MCP server rejecting
|
|
473
497
|
* a naked GET) proves the process is up; connect/DNS/timeout errors mean it is down. Replaces
|
|
474
498
|
* the customer-wide `machineRegistration` round trip when all we needed was "is LOCAL possible?". */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/simplifyqa/adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAU,MAAM,YAAY,CAAC;AAE5D,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EACV,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAC5F,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAC5D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAClE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,eAAe,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EACxF,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAClF,YAAY,EAAE,WAAW,EAAE,aAAa,EAAmB,iBAAiB,EAAE,WAAW,EACzF,cAAc,EAAE,QAAQ,EAAE,YAAY,EACtC,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,uBAAuB,EACjF,6BAA6B,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EACnE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EACxD,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EACpF,WAAW,EAAE,kBAAkB,EAAE,mBAAmB,EACrD,MAAM,uBAAuB,CAAC;AAY/B,kFAAkF;AAClF,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,iBAAiB,EAAE,WAAW,GAAG,cAAc,GAAG,UAAU,CAAC,CAAC;AA6MrG;;;;;;;;;;;;GAYG;AACH,qBAAa,iBAAiB;IAe1B,OAAO,CAAC,QAAQ,CAAC,GAAG;IAdtB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAiC;IAOjD,OAAO,CAAC,YAAY,CAAmC;IAGvD,OAAO,CAAC,oBAAoB,CAA0E;gBAGnF,GAAG,EAAE,iBAAiB,EACvC,SAAS,CAAC,EAAE,mBAAmB;IAgBjC,OAAO,CAAC,OAAO;IAOf,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,UAAU;IAmBlB;;;;uDAImD;IAC7C,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC;IAkBlC;;;;;;;;;;yFAUqF;IAC/E,YAAY,IAAI,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;YAK3C,mBAAmB;IA2BjC;;;;;oDAKgD;YAClC,gBAAgB;YAehB,kBAAkB;IAoBhC;;8DAE0D;IAC1D,OAAO,CAAC,SAAS;IAqBX,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IA0B/D;8EAC0E;IAC1E,OAAO,CAAC,iBAAiB;IAQzB;;;sGAGkG;IAC5F,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/simplifyqa/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -227,6 +227,9 @@ export class SimplifyQaAdapter {
|
|
|
227
227
|
* Keyed by `${technologyType}::${lowerCaseName}` so the same action name across two techs
|
|
228
228
|
* (e.g. "click" for WEB + ANDROID) resolves to the right numeric id. */
|
|
229
229
|
this.actionCatalogCache = null;
|
|
230
|
+
/** Populated by actionCatalog() on each fetch. Surfaced in resolveAutomationSteps error messages
|
|
231
|
+
* so a user hitting "no SQA action named X" can see what the fetch actually returned. */
|
|
232
|
+
this.actionCatalogFetchDiag = 'never fetched';
|
|
230
233
|
// ---------------------------------------------------------------------- Objects (read + delete)
|
|
231
234
|
// list/create/delete now go direct REST (same /tm/object base getObject already uses), mirroring
|
|
232
235
|
// the testcase/suite search + soft-delete convention.
|
|
@@ -254,6 +257,18 @@ export class SimplifyQaAdapter {
|
|
|
254
257
|
...(this.cfg.projectId ? { 'x-project-id': this.cfg.projectId } : {}),
|
|
255
258
|
};
|
|
256
259
|
}
|
|
260
|
+
/** Public session context for MCP viewer/recorder windows that need to authenticate the sqa-ui
|
|
261
|
+
* they load. cfg itself stays private; this getter is the one seam through which tc_edit_open
|
|
262
|
+
* and record tools can forward token + baseUrl + projectId to Electron without asking the LLM. */
|
|
263
|
+
getSessionContext() {
|
|
264
|
+
// Constructor already rejected missing baseUrl (~line 265), so `!` is safe here.
|
|
265
|
+
return {
|
|
266
|
+
token: this.cfg.token,
|
|
267
|
+
baseUrl: this.cfg.baseUrl,
|
|
268
|
+
...(this.cfg.projectId ? { projectId: this.cfg.projectId } : {}),
|
|
269
|
+
...(this.cfg.userId ? { userId: this.cfg.userId } : {}),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
257
272
|
requireProjectId() {
|
|
258
273
|
if (!this.cfg.projectId)
|
|
259
274
|
throw new Error(NEEDS_PROJECT);
|
|
@@ -470,7 +485,11 @@ export class SimplifyQaAdapter {
|
|
|
470
485
|
// Overlay currentVersionId to the resolved one and flip isCurrent flags on versions[]. Apply
|
|
471
486
|
// even when rows are empty — a caller who pinned versionId still expects the header + versions[]
|
|
472
487
|
// to reflect that choice (a legitimately-empty version shouldn't fall back to the TC's default).
|
|
473
|
-
|
|
488
|
+
// ponytail: mirror fetchStepsCollection's resolution ladder — the raw doc from /tm/testcase/getById
|
|
489
|
+
// does NOT include a top-level versionId, only a `versions[]` array. Without the versions[0] fallback,
|
|
490
|
+
// tc.currentVersionId stays undefined → tc_save emits versionsDetails.id:"" → backend mints a
|
|
491
|
+
// phantom version and orphans the saved steps.
|
|
492
|
+
const resolvedVer = versionId ?? tc.currentVersionId ?? (Array.isArray(tc.versions) ? tc.versions[0]?.id : undefined);
|
|
474
493
|
const patched = { ...withSteps, ...(resolvedVer ? { currentVersionId: resolvedVer } : {}) };
|
|
475
494
|
if (resolvedVer && patched.versions?.length) {
|
|
476
495
|
patched.versions = patched.versions.map((v) => ({ ...v, isCurrent: v.id === resolvedVer }));
|
|
@@ -2317,8 +2336,10 @@ export class SimplifyQaAdapter {
|
|
|
2317
2336
|
return this.actionCatalogCache;
|
|
2318
2337
|
const projectId = this.requireProjectId();
|
|
2319
2338
|
this.actionCatalogCache = (async () => {
|
|
2320
|
-
//
|
|
2321
|
-
//
|
|
2339
|
+
// Use the SAME filter the SQA UI uses (mobile-app/angular/.../steps.model.ts:59):
|
|
2340
|
+
// {projectId: {$in:[0, projectId]}}. Removing it caused /tm/action/search to return 0 rows in
|
|
2341
|
+
// practice — the backend endpoint apparently requires it as-is despite server-side resolvers
|
|
2342
|
+
// using only {customerId, deleted:false}. Keep the UI's filter to stay compatible.
|
|
2322
2343
|
const body = {
|
|
2323
2344
|
searchFields: [
|
|
2324
2345
|
{ column: 'deleted', value: false },
|
|
@@ -2330,12 +2351,25 @@ export class SimplifyQaAdapter {
|
|
|
2330
2351
|
};
|
|
2331
2352
|
const raw = await this.http.postJson('/tm/action/search', body);
|
|
2332
2353
|
const rows = pickArray(raw, 'data', 'content', 'actions');
|
|
2354
|
+
// Capture diagnostic snapshot so downstream errors can surface WHY the fetch returned 0 rows.
|
|
2355
|
+
try {
|
|
2356
|
+
const topKeys = raw && typeof raw === 'object' ? Object.keys(raw).slice(0, 10).join(',') : typeof raw;
|
|
2357
|
+
const totalCount = raw?.totalCount ?? raw?.total ?? 'n/a';
|
|
2358
|
+
const firstRowKeys = rows[0] && typeof rows[0] === 'object' ? Object.keys(rows[0]).slice(0, 12).join(',') : 'no-rows';
|
|
2359
|
+
this.actionCatalogFetchDiag = `rows.length=${rows.length} totalCount=${totalCount} raw.keys=[${topKeys}] firstRowKeys=[${firstRowKeys}] projectId=${projectId}`;
|
|
2360
|
+
}
|
|
2361
|
+
catch { /* diagnostic must not throw */ }
|
|
2333
2362
|
const map = new Map();
|
|
2334
2363
|
for (const row of rows) {
|
|
2335
2364
|
const idStr = str(row, 'id', '_id', 'actionId');
|
|
2336
2365
|
const name = str(row, 'name');
|
|
2337
|
-
|
|
2338
|
-
|
|
2366
|
+
// Diagnostic proved action rows have NO top-level `technology` field
|
|
2367
|
+
// (firstRowKeys=[id,name,objectRequired,objectTemplate]). SQA UI derives tech by looking up
|
|
2368
|
+
// objectTemplate → object_templates.name — we don't (would need a second fetch). Just require
|
|
2369
|
+
// id; drop the tech-in-key and let names collide across techs. Round-trip lookups (by numeric
|
|
2370
|
+
// id) are unaffected. Model-authored by-name lookups get whichever action registered last for
|
|
2371
|
+
// that name — good enough until cross-tech name collisions actually bite.
|
|
2372
|
+
if (!idStr)
|
|
2339
2373
|
continue;
|
|
2340
2374
|
const id = Number.parseInt(idStr, 10);
|
|
2341
2375
|
if (!Number.isFinite(id))
|
|
@@ -2345,14 +2379,17 @@ export class SimplifyQaAdapter {
|
|
|
2345
2379
|
const objectTemplate = typeof otRaw === 'number' ? otRaw
|
|
2346
2380
|
: typeof otRaw === 'string' && /^\d+$/.test(otRaw) ? Number.parseInt(otRaw, 10)
|
|
2347
2381
|
: undefined;
|
|
2348
|
-
|
|
2349
|
-
//
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2382
|
+
const entry = { id, objectRequired, ...(objectTemplate !== undefined ? { objectTemplate } : {}) };
|
|
2383
|
+
// Name-only key for by-name lookups (may collide across techs — see comment above).
|
|
2384
|
+
if (name) {
|
|
2385
|
+
const key = name.toLowerCase();
|
|
2386
|
+
const prev = map.get(key);
|
|
2387
|
+
const isProjectOverride = typeof row?.projectId === 'number' && row.projectId !== 0;
|
|
2388
|
+
if (!prev || isProjectOverride)
|
|
2389
|
+
map.set(key, entry);
|
|
2390
|
+
}
|
|
2391
|
+
// Id-keyed entry so numeric-actionType round-trip lookups are O(1) instead of a scan.
|
|
2392
|
+
map.set(`id::${id}`, entry);
|
|
2356
2393
|
}
|
|
2357
2394
|
return map;
|
|
2358
2395
|
})().catch((e) => { this.actionCatalogCache = null; throw e; });
|
|
@@ -2409,27 +2446,106 @@ export class SimplifyQaAdapter {
|
|
|
2409
2446
|
async resolveAutomationSteps(projectId, moduleId, tc) {
|
|
2410
2447
|
const catalog = await this.actionCatalog();
|
|
2411
2448
|
const steps = Array.isArray(tc.steps) ? tc.steps : [];
|
|
2449
|
+
// ponytail: objectId can be `undefined` here to mean "don't emit — preserve the backend value".
|
|
2450
|
+
// Used when an existing step (sourceId set) has no round-trip objectRef.id, e.g. because it was
|
|
2451
|
+
// recorded before objectRequired was flipped on the action. Emitting null would $set-clobber the DB.
|
|
2452
|
+
// actionId also nullable for control-flow / function / switch / function_end — those steps don't
|
|
2453
|
+
// have an action; buildAutomationPayload omits action/object emit when actionId is null.
|
|
2412
2454
|
const out = [];
|
|
2413
2455
|
for (let i = 0; i < steps.length; i++) {
|
|
2414
2456
|
const s = steps[i];
|
|
2457
|
+
// ponytail: control-flow / function / switch / function_end steps have no action to resolve.
|
|
2458
|
+
// Push a null-actionId slot so index alignment survives; mapper checks and skips action/object
|
|
2459
|
+
// emit for these. Without this, IF/FOR/WHILE/FUNCTION steps throw "missing actionType" on save.
|
|
2460
|
+
if (s.stepType === 'control_flow' || s.stepType === 'function' || s.stepType === 'function_end' || s.stepType === 'switch' || s.stepType === 'manual') {
|
|
2461
|
+
out.push({ actionId: null, objectId: null });
|
|
2462
|
+
continue;
|
|
2463
|
+
}
|
|
2415
2464
|
const tech = (TECH_BY_STEPTYPE[s.stepType] ?? deriveTcType(tc)).toUpperCase();
|
|
2416
2465
|
const name = (s.actionType ?? '').trim();
|
|
2417
2466
|
if (!name) {
|
|
2418
2467
|
throw new Error(`Automation step ${i + 1} ("${s.description}"): missing actionType — the neutral Step.actionType must name an SQA action (e.g. "Click", "Type Text", "Launch Application").`);
|
|
2419
2468
|
}
|
|
2420
|
-
|
|
2469
|
+
// Round-trip support: a TC hydrated via getTestCase carries actionType = String(actionId), so
|
|
2470
|
+
// on save we may see a numeric string here. Catalog is dual-keyed: `id::N` for round-trip,
|
|
2471
|
+
// `<lowercase name>` for model-authored by-name lookups. Tech is not in the key because
|
|
2472
|
+
// action rows have no top-level `technology` field (SQA UI resolves via objectTemplate lookup).
|
|
2473
|
+
const isNumericId = /^\d+$/.test(name);
|
|
2474
|
+
let meta = isNumericId
|
|
2475
|
+
? catalog.get(`id::${Number.parseInt(name, 10)}`)
|
|
2476
|
+
: catalog.get(name.toLowerCase());
|
|
2477
|
+
// Fallback: if the by-id scan misses (action created in a different project and thus not in
|
|
2478
|
+
// the UI's projectId-filtered catalog), fetch it directly via GET /tm/action/:id. Only tries
|
|
2479
|
+
// for numeric-id lookups — model-authored name lookups have no id to fetch.
|
|
2480
|
+
let fallbackDiag = '';
|
|
2481
|
+
if (!meta && isNumericId) {
|
|
2482
|
+
try {
|
|
2483
|
+
const raw = await this.http.getJson(`/tm/action/${Number.parseInt(name, 10)}`);
|
|
2484
|
+
const row = raw?.data && typeof raw.data === 'object' ? raw.data
|
|
2485
|
+
: Array.isArray(raw?.data) ? raw.data[0]
|
|
2486
|
+
: Array.isArray(raw) ? raw[0]
|
|
2487
|
+
: raw;
|
|
2488
|
+
const idStr = str(row, 'id', '_id', 'actionId');
|
|
2489
|
+
// ponytail: no `technology` gate. Catalog builder proved action rows have no top-level
|
|
2490
|
+
// technology field (see comment at ~line 2357); requiring it here made the fallback
|
|
2491
|
+
// never succeed — every cross-project action id threw "no SQA action named X".
|
|
2492
|
+
if (idStr) {
|
|
2493
|
+
const otRaw = row?.objectTemplate;
|
|
2494
|
+
const objectTemplate = typeof otRaw === 'number' ? otRaw
|
|
2495
|
+
: typeof otRaw === 'string' && /^\d+$/.test(otRaw) ? Number.parseInt(otRaw, 10)
|
|
2496
|
+
: undefined;
|
|
2497
|
+
meta = {
|
|
2498
|
+
id: Number.parseInt(idStr, 10),
|
|
2499
|
+
objectRequired: row?.objectRequired === true,
|
|
2500
|
+
...(objectTemplate !== undefined ? { objectTemplate } : {}),
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2503
|
+
else {
|
|
2504
|
+
fallbackDiag = ` [GET /tm/action/${name} returned no usable row (idStr=null). Raw keys: ${row && typeof row === 'object' ? Object.keys(row).join(',') : typeof row}]`;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
catch (e) {
|
|
2508
|
+
fallbackDiag = ` [GET /tm/action/${name} failed: ${e.message}]`;
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2421
2511
|
if (!meta) {
|
|
2422
|
-
throw new Error(`Automation step ${i + 1}: no SQA action named "${name}" for technology ${tech}.
|
|
2512
|
+
throw new Error(`Automation step ${i + 1}: no SQA action named "${name}" for technology ${tech}. Catalog size=${catalog.size}, numericId=${isNumericId}. FetchDiag: ${this.actionCatalogFetchDiag}.${fallbackDiag}`);
|
|
2423
2513
|
}
|
|
2424
2514
|
let objectId = null;
|
|
2425
2515
|
if (meta.objectRequired) {
|
|
2426
|
-
|
|
2427
|
-
|
|
2516
|
+
// Resolution ladder (first match wins):
|
|
2517
|
+
// 1. objectRef.id set → use directly (round-trip OR LLM referencing a known object)
|
|
2518
|
+
// 2. sourceId set but no objectRef.id → preserve DB value (existing step, backend may
|
|
2519
|
+
// have null historically; emitting anything would $set-clobber)
|
|
2520
|
+
// 3. objectRef.name only (no locators) → project-scoped name lookup (LLM added a step
|
|
2521
|
+
// referencing an existing Object Repository entry by name — mirrors UI dropdown)
|
|
2522
|
+
// 4. objectRef.name + locators → get-or-create via createObjectSeq (recorder path)
|
|
2523
|
+
// 5. nothing usable → throw with the three-option error so the caller can pick
|
|
2524
|
+
const roundTripId = s.objectRef?.id ? Number.parseInt(String(s.objectRef.id), 10) : NaN;
|
|
2525
|
+
if (Number.isFinite(roundTripId)) {
|
|
2526
|
+
objectId = roundTripId;
|
|
2527
|
+
}
|
|
2528
|
+
else if (s.sourceId) {
|
|
2529
|
+
objectId = undefined;
|
|
2530
|
+
}
|
|
2531
|
+
else if (s.objectRef?.name && !(s.objectRef.locators?.length)) {
|
|
2532
|
+
const found = await this.findObjectIdByName(projectId, s.objectRef.name);
|
|
2533
|
+
if (found != null) {
|
|
2534
|
+
objectId = found;
|
|
2535
|
+
}
|
|
2536
|
+
else {
|
|
2537
|
+
throw new Error(`Automation step ${i + 1} ("${name}"): no object named "${s.objectRef.name}" found in this project. Either pass objectRef.id to reuse an existing one, or add objectRef.locators:[{name, value}, ...] to create a new object with that name.`);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
else if (s.objectRef?.name && s.objectRef.locators?.length) {
|
|
2541
|
+
if (meta.objectTemplate == null) {
|
|
2542
|
+
throw new Error(`Automation step ${i + 1} ("${name}"): action ${meta.id} has no objectTemplate — cannot create/reuse an object without knowing which template it belongs to.`);
|
|
2543
|
+
}
|
|
2544
|
+
objectId = await this.resolveObjectId(s.objectRef, projectId, moduleId, tech, meta.objectTemplate);
|
|
2428
2545
|
}
|
|
2429
|
-
|
|
2430
|
-
throw new Error(`Automation step ${i + 1} ("${name}"): action
|
|
2546
|
+
else {
|
|
2547
|
+
throw new Error(`Automation step ${i + 1} ("${name}"): action requires an object but Step.objectRef is missing. Provide objectRef:{id} to reuse a known one, objectRef:{name} to look one up by name in the project, or objectRef:{name, locators:[{name, value}]} to create a new one.`);
|
|
2431
2548
|
}
|
|
2432
|
-
objectId = await this.resolveObjectId(s.objectRef, projectId, moduleId, tech, meta.objectTemplate);
|
|
2433
2549
|
}
|
|
2434
2550
|
out.push({ actionId: meta.id, objectId });
|
|
2435
2551
|
}
|
|
@@ -2458,6 +2574,28 @@ export class SimplifyQaAdapter {
|
|
|
2458
2574
|
}
|
|
2459
2575
|
return null;
|
|
2460
2576
|
}
|
|
2577
|
+
/** Case-insensitive project-scoped lookup of an existing object by `disname` (the UI's Name).
|
|
2578
|
+
* Lets tc_draft_update pass `objectRef:{name}` for an already-defined object without also
|
|
2579
|
+
* supplying locators — mirrors what a user picking from the Object Repository dropdown does. */
|
|
2580
|
+
async findObjectIdByName(projectId, name) {
|
|
2581
|
+
const body = this.searchBody([
|
|
2582
|
+
{ column: 'disname', value: name },
|
|
2583
|
+
{ column: 'projectId', value: projectId },
|
|
2584
|
+
{ column: 'deleted', value: false },
|
|
2585
|
+
], { startIndex: 0, limit: 10 });
|
|
2586
|
+
body['searchFields'][0]['ignorecase'] = true;
|
|
2587
|
+
const raw = await this.http.postJson('/tm/object/search', body);
|
|
2588
|
+
const rows = pickArray(raw, 'data', 'content', 'objects');
|
|
2589
|
+
for (const row of rows) {
|
|
2590
|
+
const rowName = str(row, 'disname');
|
|
2591
|
+
if (rowName?.toLowerCase() === name.toLowerCase()) {
|
|
2592
|
+
const id = str(row, 'id', '_id');
|
|
2593
|
+
if (id)
|
|
2594
|
+
return Number.parseInt(id, 10);
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
return null;
|
|
2598
|
+
}
|
|
2461
2599
|
async getOrCreateParameter(projectId, moduleId, name, defaultValue) {
|
|
2462
2600
|
const existing = await this.findParameterId(projectId, name);
|
|
2463
2601
|
if (existing != null)
|
|
@@ -2888,7 +3026,19 @@ export class SimplifyQaAdapter {
|
|
|
2888
3026
|
}
|
|
2889
3027
|
// ---------------------------------------------------------------------- Actions catalog
|
|
2890
3028
|
async actionsCatalog(platform) {
|
|
2891
|
-
|
|
3029
|
+
// Build from the live backend catalog (already cached per-connection). Each entry gets its
|
|
3030
|
+
// numeric id + objectRequired flag so callers can round-trip actionType ↔ actionId. Ignores
|
|
3031
|
+
// `platform` — action rows carry no top-level technology; tech disambiguation is a follow-up
|
|
3032
|
+
// (would need an object_templates lookup). Skips the `id::N` reverse-lookup keys.
|
|
3033
|
+
void platform;
|
|
3034
|
+
const catalog = await this.actionCatalog();
|
|
3035
|
+
const defs = [];
|
|
3036
|
+
for (const [key, meta] of catalog) {
|
|
3037
|
+
if (key.startsWith('id::'))
|
|
3038
|
+
continue; // reverse-lookup entry — already covered by name key
|
|
3039
|
+
defs.push({ actionType: key, platform: 'unknown', id: meta.id, objectRequired: meta.objectRequired });
|
|
3040
|
+
}
|
|
3041
|
+
return defs;
|
|
2892
3042
|
}
|
|
2893
3043
|
// ---------------------------------------------------------------------- Testdata generation [Step 3]
|
|
2894
3044
|
/** Fetch the default (template) iteration row for a TC. Fills gaps from the raw TC:
|
|
@@ -3314,11 +3464,10 @@ export class SimplifyQaAdapter {
|
|
|
3314
3464
|
// SUITE runs are not yet ported and still hop (or error clearly).
|
|
3315
3465
|
async startRun(target, opts) {
|
|
3316
3466
|
const projectId = this.requireProjectId();
|
|
3467
|
+
if (target.suiteId && !target.tcId) {
|
|
3468
|
+
return this.startSuiteRun(target.suiteId, opts);
|
|
3469
|
+
}
|
|
3317
3470
|
if (!target.tcId) {
|
|
3318
|
-
if (target.suiteId) {
|
|
3319
|
-
throw new Error('startRun: SUITE runs are not yet ported to direct REST — set QA_MCP_URL so common-mcp ' +
|
|
3320
|
-
'can hop to the SimplifyQA agent for suite dispatch, or run a single TC.');
|
|
3321
|
-
}
|
|
3322
3471
|
throw new Error('startRun: target.tcId (or target.suiteId) is required.');
|
|
3323
3472
|
}
|
|
3324
3473
|
const tcId = target.tcId;
|
|
@@ -3492,6 +3641,192 @@ export class SimplifyQaAdapter {
|
|
|
3492
3641
|
killType: 'STOP_EXECUTION',
|
|
3493
3642
|
});
|
|
3494
3643
|
}
|
|
3644
|
+
/** SUITE dispatch. Mirrors `startRun` above (which handles TC) — same `/exec/execution` endpoint,
|
|
3645
|
+
* same LOCAL-agent fast-fail, same project-name / environment resolution. What differs is on the
|
|
3646
|
+
* wire: `type: 'SUITE'`, `executionType` sourced from `suite.suiteType`, `mode` defaulting to
|
|
3647
|
+
* `suite.executionType`, a populated `globalConfiguration` block, and one `executionItems` entry
|
|
3648
|
+
* PER member — each carrying the suite row's persisted `versionId` / `stepId` / `platform` / `seq`
|
|
3649
|
+
* (NOT a re-derived random step id). See sqa-ui execution-popup-modal.component.ts:621-1105 SU
|
|
3650
|
+
* branch for the authoritative UI builder; every field below traces back to a line there. */
|
|
3651
|
+
async startSuiteRun(suiteId, opts) {
|
|
3652
|
+
const projectId = this.requireProjectId();
|
|
3653
|
+
const suiteNum = Number.parseInt(numericId(suiteId), 10);
|
|
3654
|
+
if (!Number.isFinite(suiteNum))
|
|
3655
|
+
throw new Error(`startRun: could not resolve numeric id for ${suiteId}.`);
|
|
3656
|
+
const rawSuite = await this.fetchRawSuite(suiteId);
|
|
3657
|
+
if (!rawSuite || Object.keys(rawSuite).length === 0) {
|
|
3658
|
+
throw new Error(`startRun: suite ${suiteId} not found.`);
|
|
3659
|
+
}
|
|
3660
|
+
// Effective mode: caller pick wins (matches our tool's cascade UX); fall back to the suite doc's
|
|
3661
|
+
// stored `executionType`, which the UI reads unconditionally (execution-popup-modal.ts:668).
|
|
3662
|
+
const mode = (opts.mode ?? String(rawSuite.executionType ?? 'LOCAL')).toUpperCase();
|
|
3663
|
+
if (mode === 'LOCAL' && !(await this.probeLocalAgent())) {
|
|
3664
|
+
throw new Error(`startRun (LOCAL): SimplifyQA desktop agent is not reachable at ` +
|
|
3665
|
+
`${this.cfg.url ?? '(no agent URL configured — set QA_MCP_URL)'}. Start the desktop ` +
|
|
3666
|
+
`agent, then try again. Use mode:'cloud' to run on a cloud runner instead — it does not ` +
|
|
3667
|
+
`need the desktop agent.`);
|
|
3668
|
+
}
|
|
3669
|
+
const projectName = await this.lookupProjectName(projectId);
|
|
3670
|
+
if (!projectName) {
|
|
3671
|
+
throw new Error(`startRun: could not resolve project name for projectId=${projectId}. ` +
|
|
3672
|
+
`The token may not have read access to this project.`);
|
|
3673
|
+
}
|
|
3674
|
+
const env = await this.resolveEnvironment(projectId, opts.environment);
|
|
3675
|
+
const suiteType = String(rawSuite.suiteType ?? 'AUTOMATION').toUpperCase();
|
|
3676
|
+
// Build one executionItems entry per suite row, in suite order. Silently skip rows whose TC no
|
|
3677
|
+
// longer exists (matches UI :880 `if (!data) return;`). Every item uses the suite row's stored
|
|
3678
|
+
// versionId + stepId + per-mode platform — regenerating any of those splits step correlation
|
|
3679
|
+
// and breaks the agent's iteration join. Missing platform for a row is not an error here (UI
|
|
3680
|
+
// sends `[]`) — the agent tolerates it for MANUAL suites; AUTOMATION suites without a saved
|
|
3681
|
+
// platform were misconfigured upstream and would fail regardless.
|
|
3682
|
+
const members = Array.isArray(rawSuite.testCases) ? rawSuite.testCases : [];
|
|
3683
|
+
const executionItems = [];
|
|
3684
|
+
let seqCounter = 0;
|
|
3685
|
+
for (const row of members) {
|
|
3686
|
+
seqCounter++;
|
|
3687
|
+
const memberTcId = row?.testcaseId;
|
|
3688
|
+
if (memberTcId === undefined || memberTcId === null)
|
|
3689
|
+
continue;
|
|
3690
|
+
let rawTc;
|
|
3691
|
+
try {
|
|
3692
|
+
rawTc = await this.fetchRawTestcase(String(memberTcId));
|
|
3693
|
+
}
|
|
3694
|
+
catch {
|
|
3695
|
+
continue;
|
|
3696
|
+
}
|
|
3697
|
+
if (!rawTc || Object.keys(rawTc).length === 0)
|
|
3698
|
+
continue;
|
|
3699
|
+
const tcType = String(str(rawTc, 'tcType') ?? 'WEB').toUpperCase();
|
|
3700
|
+
const versions = Array.isArray(rawTc?.versions) ? rawTc.versions : [];
|
|
3701
|
+
const rowVersionId = row?.versionId ?? row?.version;
|
|
3702
|
+
const versionMatch = rowVersionId
|
|
3703
|
+
? versions.find((v) => str(v, 'id', '_id') === String(rowVersionId) || str(v, 'name') === String(rowVersionId))
|
|
3704
|
+
: undefined;
|
|
3705
|
+
const versionId = str(versionMatch, 'id', '_id') ?? (rowVersionId !== undefined ? String(rowVersionId) : '');
|
|
3706
|
+
const versionName = str(versionMatch, 'name') ?? str(rawTc, 'versionName', 'selectedVersionName') ?? 'v1.0';
|
|
3707
|
+
// Per-mode platform from the suite row. UI reads `data1.executionPlatform[mode]` (:882-965);
|
|
3708
|
+
// when saved, it's a single object → wrap in `[obj]`; if the row didn't save one for this
|
|
3709
|
+
// mode, send `[]` and let the backend reject/accept per its own rules.
|
|
3710
|
+
const savedPlatform = row?.executionPlatform?.[mode];
|
|
3711
|
+
let platform = [];
|
|
3712
|
+
if (savedPlatform && typeof savedPlatform === 'object') {
|
|
3713
|
+
const p = {
|
|
3714
|
+
name: savedPlatform.name,
|
|
3715
|
+
id: savedPlatform.id ?? savedPlatform.name,
|
|
3716
|
+
technologyType: String(savedPlatform.technologyType ?? tcType).toUpperCase(),
|
|
3717
|
+
type: savedPlatform.type ?? 'NONE',
|
|
3718
|
+
};
|
|
3719
|
+
if (savedPlatform.cloudDeviceType)
|
|
3720
|
+
p['cloudDeviceType'] = savedPlatform.cloudDeviceType;
|
|
3721
|
+
if (savedPlatform.osVersion)
|
|
3722
|
+
p['osVersion'] = savedPlatform.osVersion;
|
|
3723
|
+
platform = [p];
|
|
3724
|
+
}
|
|
3725
|
+
else if (['API', 'MAINFRAME', 'SAP', 'DESKTOP', 'DATABASE'].includes(tcType)) {
|
|
3726
|
+
platform = [{ name: tcType, id: tcType, technologyType: tcType, type: 'NONE' }];
|
|
3727
|
+
}
|
|
3728
|
+
const moduleIdNum = typeof rawTc?.moduleId === 'number' ? rawTc.moduleId : 0;
|
|
3729
|
+
let moduleName = '';
|
|
3730
|
+
if (moduleIdNum > 0) {
|
|
3731
|
+
try {
|
|
3732
|
+
const body = this.searchBody([{ column: 'id', value: moduleIdNum }, { column: 'deleted', value: false }], { selectFields: ['id', 'name'], limit: 1 });
|
|
3733
|
+
const raw = await this.http.postJson('/pm/module/search', body);
|
|
3734
|
+
const rows = pickArray(raw, 'data', 'content', 'modules');
|
|
3735
|
+
moduleName = str(rows[0], 'name') ?? '';
|
|
3736
|
+
}
|
|
3737
|
+
catch { /* leave empty */ }
|
|
3738
|
+
}
|
|
3739
|
+
executionItems.push({
|
|
3740
|
+
id: typeof rawTc?.id === 'number' ? rawTc.id : Number.parseInt(numericId(String(memberTcId)), 10),
|
|
3741
|
+
name: str(rawTc, 'name', 'title') ?? '',
|
|
3742
|
+
description: str(rawTc, 'description') ?? '',
|
|
3743
|
+
versionId,
|
|
3744
|
+
versionName,
|
|
3745
|
+
seq: typeof row?.seq === 'number' ? row.seq : seqCounter,
|
|
3746
|
+
stepId: row?.stepId ?? '', // MUST come from the suite row, never regenerated
|
|
3747
|
+
stepCount: -1, // suite items send -1 (TC sends 0)
|
|
3748
|
+
technologyType: tcType,
|
|
3749
|
+
type: 'TC', // every item is 'TC'; suite-ness lives at top-level type
|
|
3750
|
+
platform,
|
|
3751
|
+
projectId,
|
|
3752
|
+
projectName,
|
|
3753
|
+
configuration: {},
|
|
3754
|
+
iterationsSelected: [],
|
|
3755
|
+
moduleId: moduleIdNum,
|
|
3756
|
+
userstoryId: typeof rawTc?.userstoryId === 'number' ? rawTc.userstoryId : 0,
|
|
3757
|
+
tags: Array.isArray(rawTc?.tags) ? rawTc.tags : [],
|
|
3758
|
+
code: str(rawTc, 'code') ?? '',
|
|
3759
|
+
prerequisite: str(rawTc, 'prerequisite', 'preconditions') ?? '',
|
|
3760
|
+
moduleName,
|
|
3761
|
+
userstoryName: '',
|
|
3762
|
+
});
|
|
3763
|
+
}
|
|
3764
|
+
if (executionItems.length === 0) {
|
|
3765
|
+
throw new Error(`startRun: suite ${suiteId} has no runnable test cases (all members were deleted, ` +
|
|
3766
|
+
`unreadable, or filtered out).`);
|
|
3767
|
+
}
|
|
3768
|
+
// agentId derivation (mirrors UI :677). LOCAL always uses this machine's agent; REMOTE/CLOUD
|
|
3769
|
+
// pin the suite doc's saved `agentId` (or null when the suite wasn't pinned). CLOUD_AGENT
|
|
3770
|
+
// fallback (:771-774) promotes `cloudType` to 'SQA'.
|
|
3771
|
+
let agentId;
|
|
3772
|
+
if (mode === 'LOCAL') {
|
|
3773
|
+
agentId = await this.resolveLocalAgentId(opts.agentId);
|
|
3774
|
+
}
|
|
3775
|
+
else if (mode === 'REMOTE') {
|
|
3776
|
+
const preferred = opts.agentId ?? (typeof rawSuite.agentId === 'string' ? rawSuite.agentId : undefined);
|
|
3777
|
+
agentId = preferred ? await this.resolveRemoteAgentId(preferred) : null;
|
|
3778
|
+
}
|
|
3779
|
+
else {
|
|
3780
|
+
agentId = typeof rawSuite.agentId === 'string' && rawSuite.agentId.length > 0 ? rawSuite.agentId : null;
|
|
3781
|
+
}
|
|
3782
|
+
let cloudType = mode === 'CLOUD' ? 'VM' : 'NONE';
|
|
3783
|
+
if (!agentId && typeof rawSuite.cloudAgentId === 'string' && rawSuite.cloudAgentId.length > 0) {
|
|
3784
|
+
agentId = rawSuite.cloudAgentId;
|
|
3785
|
+
cloudType = 'SQA';
|
|
3786
|
+
}
|
|
3787
|
+
const gc = rawSuite.globalConfiguration ?? {};
|
|
3788
|
+
const body = {
|
|
3789
|
+
projectId,
|
|
3790
|
+
environmentType: env,
|
|
3791
|
+
executionStyle: (opts.strategy ?? 'serial').toUpperCase(),
|
|
3792
|
+
executionOS: this.detectExecutionOS(),
|
|
3793
|
+
executionTypeId: suiteNum,
|
|
3794
|
+
executionTypeName: str(rawSuite, 'name') ?? '',
|
|
3795
|
+
executionTypeCode: str(rawSuite, 'code') ?? '',
|
|
3796
|
+
mailersList: rawSuite.mailersList ?? {},
|
|
3797
|
+
cloudType,
|
|
3798
|
+
mode,
|
|
3799
|
+
executionCategory: 'EXECUTION',
|
|
3800
|
+
releaseId: opts.releaseId ? Number.parseInt(opts.releaseId, 10) || 0 : 0,
|
|
3801
|
+
iterationId: opts.sprintId ? Number.parseInt(opts.sprintId, 10) || 0 : 0,
|
|
3802
|
+
executionTime: '',
|
|
3803
|
+
status: 'INPROGRESS',
|
|
3804
|
+
iterationsSelected: [],
|
|
3805
|
+
result: '',
|
|
3806
|
+
globalConfiguration: {
|
|
3807
|
+
TERMINATE_ON_FAIL: opts.terminateOnFailure ?? gc.TERMINATE_ON_FAIL ?? false,
|
|
3808
|
+
NEW_BROWSER_SESSION: gc.NEW_BROWSER_SESSION ?? false,
|
|
3809
|
+
RE_EXECUTION_COUNT: gc.RE_EXECUTION_COUNT ?? 0,
|
|
3810
|
+
RE_EXECUTION_TYPE: gc.RE_EXECUTION_TYPE ?? 'AFTER_TESTCASE',
|
|
3811
|
+
MINIMUM_CONTAINER_COUNT: gc.MINIMUM_CONTAINER_COUNT ?? 1,
|
|
3812
|
+
MAXIMUM_CONTAINER_COUNT: opts.maxConcurrent ?? gc.MAXIMUM_CONTAINER_COUNT ?? 5,
|
|
3813
|
+
},
|
|
3814
|
+
isRecordingEnabled: false,
|
|
3815
|
+
offset: -(new Date().getTimezoneOffset()),
|
|
3816
|
+
type: 'SUITE',
|
|
3817
|
+
executionType: suiteType,
|
|
3818
|
+
agentId,
|
|
3819
|
+
executionItems,
|
|
3820
|
+
};
|
|
3821
|
+
const resp = await this.http.postJson('/exec/execution', body);
|
|
3822
|
+
const d = resp?.data ?? resp;
|
|
3823
|
+
const runId = str(d, 'id', 'executionId', '_id') ??
|
|
3824
|
+
(typeof d?.id === 'number' ? String(d.id) : undefined) ??
|
|
3825
|
+
(typeof d?.executionId === 'number' ? String(d.executionId) : undefined);
|
|
3826
|
+
if (!runId)
|
|
3827
|
+
throw new Error('startRun: backend accepted suite trigger but returned no run id.');
|
|
3828
|
+
return { runId };
|
|
3829
|
+
}
|
|
3495
3830
|
/** GET the agent /mcp with a 1s timeout. Any HTTP response (incl. 4xx from an MCP server rejecting
|
|
3496
3831
|
* a naked GET) proves the process is up; connect/DNS/timeout errors mean it is down. Replaces
|
|
3497
3832
|
* the customer-wide `machineRegistration` round trip when all we needed was "is LOCAL possible?". */
|