@nbakka/mcp-appium 3.0.22 → 3.0.24

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.
Files changed (2) hide show
  1. package/lib/server.js +252 -25
  2. package/package.json +1 -1
package/lib/server.js CHANGED
@@ -215,32 +215,259 @@ const createMcpServer = () => {
215
215
  // }
216
216
  // );
217
217
 
218
- tool(
219
- "mobile_learn_testcases_generation_context",
220
- "Reads previously saved app context notes and returns them so they can be used to validate or update test cases. This should be executed before generating manual test cases.",
221
- {},
222
- async () => {
223
- try {
224
- // Read app context notes from file
225
- const notesFilePath = path.join(__dirname, "app_context.txt");
226
- const fileContent = await fs.readFile(notesFilePath, "utf-8");
227
-
228
- const notes = fileContent
229
- .split("\n")
230
- .map(line => line.trim())
231
- .filter(line => line.length > 0);
232
-
233
- if (notes.length === 0) {
234
- return "No app context notes found.";
235
- }
236
-
237
- return `App context loaded for testcase generation: ${JSON.stringify({ notes })}`;
238
- } catch (error) {
239
- return `Error reading app context notes: ${error.message}`;
240
- }
241
- }
242
- );
218
+ // Tool 1: Fetch incomplete test case
219
+ tool(
220
+ "mobile_fetch_incomplete_testcase",
221
+ "Fetches the first test case from a Google Sheet that has blank test steps but has a value in 'UT v/s Automation' column. Returns the test scenario and row number for later update.",
222
+ {
223
+ sheetName: zod_1.z.string().describe("The name of the Google Sheet tab to fetch test case data from").default("TestCases"),
224
+ },
225
+ async ({ sheetName }) => {
226
+ try {
227
+ // Load Google Sheets credentials
228
+ const keyFile = path.join(os.homedir(), 'Desktop', 'secret.json');
229
+
230
+ const auth = new google.auth.GoogleAuth({
231
+ keyFile,
232
+ scopes: ['https://www.googleapis.com/auth/spreadsheets.readonly'],
233
+ });
234
+
235
+ const authClient = await auth.getClient();
236
+ const sheets = google.sheets({ version: 'v4', auth: authClient });
237
+ const spreadsheetId = '1jAilVUeQW99JUYj1KL4jovoxeWGUnsIcY_nMJR5H6dc';
238
+
239
+ const range = `${sheetName}!A1:Z1000`;
240
+ const res = await sheets.spreadsheets.values.get({ spreadsheetId, range });
241
+ const rows = res.data.values;
242
+
243
+ if (!rows || rows.length === 0) {
244
+ return `Sheet "${sheetName}" is empty or does not exist.`;
245
+ }
246
+
247
+ // Map headers (trimmed, case-sensitive)
248
+ const header = rows[0].map(h => h.toString().trim());
249
+
250
+ // Find required column indices
251
+ const testCasesIdx = header.indexOf('Testcases');
252
+ const testStepsIdx = header.indexOf('Test Steps');
253
+ const utVsAutomationIdx = header.indexOf('UT v/s Automation');
254
+
255
+ if (testCasesIdx === -1 || testStepsIdx === -1 || utVsAutomationIdx === -1) {
256
+ return `Required columns "Testcases", "Test Steps", and/or "UT v/s Automation" not found in sheet "${sheetName}". Available columns: ${header.join(', ')}`;
257
+ }
258
+
259
+ // Find first row with empty test steps and non-empty UT v/s Automation
260
+ for (let i = 1; i < rows.length; i++) {
261
+ const row = rows[i];
262
+ const testSteps = row[testStepsIdx] ? row[testStepsIdx].toString().trim() : '';
263
+ const utVsAutomation = row[utVsAutomationIdx] ? row[utVsAutomationIdx].toString().trim() : '';
264
+ const testScenario = row[testCasesIdx] ? row[testCasesIdx].toString().trim() : '';
265
+
266
+ // Check if test steps are empty, UT v/s Automation is not empty, and test scenario exists
267
+ if (!testSteps && utVsAutomation && testScenario) {
268
+ // Convert column index to letter (A, B, C, etc.)
269
+ const columnLetter = String.fromCharCode(65 + testStepsIdx);
270
+ const rowNumber = i + 1; // +1 because spreadsheet rows are 1-indexed
271
+
272
+ return JSON.stringify({
273
+ testScenario: testScenario,
274
+ sheetName: sheetName,
275
+ rowNumber: rowNumber,
276
+ columnLetter: columnLetter,
277
+ cellReference: `${columnLetter}${rowNumber}`
278
+ });
279
+ }
280
+ }
281
+
282
+ return `No test cases found with blank test steps and non-empty "UT v/s Automation" in sheet "${sheetName}".`;
283
+
284
+ } catch (error) {
285
+ return `Error fetching incomplete test case: ${error.message}`;
286
+ }
287
+ }
288
+ );
289
+
290
+ // Tool 2: Update Test Steps
291
+ tool(
292
+ "mobile_update_test_steps",
293
+ "Updates the Test Steps cell in Google Sheet for a specific test case. Use the sheetName, rowNumber, and columnLetter obtained from mobile_fetch_incomplete_testcase tool.",
294
+ {
295
+ sheetName: zod_1.z.string().describe("The name of the Google Sheet tab"),
296
+ rowNumber: zod_1.z.number().describe("The row number to update (from fetch tool)"),
297
+ columnLetter: zod_1.z.string().describe("The column letter for Test Steps (from fetch tool)"),
298
+ testSteps: zod_1.z.string().describe("The test steps content to write into the cell"),
299
+ },
300
+ async ({ sheetName, rowNumber, columnLetter, testSteps }) => {
301
+ try {
302
+ // Load Google Sheets credentials with write permissions
303
+ const keyFile = path.join(os.homedir(), 'Desktop', 'secret.json');
243
304
 
305
+ const auth = new google.auth.GoogleAuth({
306
+ keyFile,
307
+ scopes: ['https://www.googleapis.com/auth/spreadsheets'], // Full read/write access
308
+ });
309
+
310
+ const authClient = await auth.getClient();
311
+ const sheets = google.sheets({ version: 'v4', auth: authClient });
312
+ const spreadsheetId = '1jAilVUeQW99JUYj1KL4jovoxeWGUnsIcY_nMJR5H6dc';
313
+
314
+ // Construct the cell reference (e.g., "TestCases!C5")
315
+ const cellReference = `${sheetName}!${columnLetter}${rowNumber}`;
316
+
317
+ // Update the cell
318
+ const updateRes = await sheets.spreadsheets.values.update({
319
+ spreadsheetId,
320
+ range: cellReference,
321
+ valueInputOption: 'RAW',
322
+ resource: {
323
+ values: [[testSteps]]
324
+ }
325
+ });
326
+
327
+ if (updateRes.status === 200) {
328
+ return `Successfully updated Test Steps at ${cellReference} with content: "${testSteps.substring(0, 100)}${testSteps.length > 100 ? '...' : ''}"`;
329
+ } else {
330
+ return `Failed to update Test Steps. Status: ${updateRes.status}`;
331
+ }
332
+
333
+ } catch (error) {
334
+ return `Error updating test steps: ${error.message}`;
335
+ }
336
+ }
337
+ );
338
+
339
+ tool(
340
+ "mobile_learn_teststeps_generation_guidelines",
341
+ "Reads previously saved test steps generation guidelines and returns them so they can be used to validate or update test steps. This should be executed before generating manual test steps.",
342
+ {},
343
+ async () => {
344
+ try {
345
+ // Read test steps generation guidelines from Desktop
346
+ const guidelinesFilePath = path.join(os.homedir(), 'Desktop', 'teststeps_generation_guidelines.txt');
347
+ const fileContent = await fs.readFile(guidelinesFilePath, "utf-8");
348
+
349
+ const guidelines = fileContent
350
+ .split("\n")
351
+ .map(line => line.trim())
352
+ .filter(line => line.length > 0);
353
+
354
+ if (guidelines.length === 0) {
355
+ return "No test steps generation guidelines found.";
356
+ }
357
+
358
+ return `Test steps generation guidelines loaded: ${JSON.stringify({ guidelines })}`;
359
+ } catch (error) {
360
+ return `Error reading test steps generation guidelines: ${error.message}`;
361
+ }
362
+ }
363
+ );
364
+
365
+ // Tool 3: Update Test Scenario (Always appends with green color)
366
+ tool(
367
+ "mobile_update_test_scenario",
368
+ "Updates the Test Scenario (Testcases column) in Google Sheet for outdated scenarios. Always keeps the existing scenario and adds the updated scenario in green color on a new line.",
369
+ {
370
+ sheetName: zod_1.z.string().describe("The name of the Google Sheet tab"),
371
+ rowNumber: zod_1.z.number().describe("The row number to update"),
372
+ columnLetter: zod_1.z.string().describe("The column letter for Testcases column"),
373
+ updatedScenario: zod_1.z.string().describe("The updated test scenario content"),
374
+ },
375
+ async ({ sheetName, rowNumber, columnLetter, updatedScenario }) => {
376
+ try {
377
+ // Load Google Sheets credentials with write permissions
378
+ const keyFile = path.join(os.homedir(), 'Desktop', 'secret.json');
379
+
380
+ const auth = new google.auth.GoogleAuth({
381
+ keyFile,
382
+ scopes: ['https://www.googleapis.com/auth/spreadsheets'],
383
+ });
384
+
385
+ const authClient = await auth.getClient();
386
+ const sheets = google.sheets({ version: 'v4', auth: authClient });
387
+ const spreadsheetId = '1jAilVUeQW99JUYj1KL4jovoxeWGUnsIcY_nMJR5H6dc';
388
+
389
+ const cellReference = `${sheetName}!${columnLetter}${rowNumber}`;
390
+
391
+ // First, read the existing scenario
392
+ const getRes = await sheets.spreadsheets.values.get({
393
+ spreadsheetId,
394
+ range: cellReference
395
+ });
396
+
397
+ const existingScenario = getRes.data.values && getRes.data.values[0] && getRes.data.values[0][0]
398
+ ? getRes.data.values[0][0]
399
+ : '';
400
+
401
+ if (!existingScenario) {
402
+ return `No existing scenario found at ${cellReference}. Cannot append to empty cell.`;
403
+ }
404
+
405
+ // Combine: existing scenario + newline + updated scenario
406
+ const combinedContent = `${existingScenario}\n${updatedScenario}`;
407
+
408
+ // Get sheet ID dynamically
409
+ const sheetsMetadata = await sheets.spreadsheets.get({ spreadsheetId });
410
+ const sheetId = sheetsMetadata.data.sheets.find(s => s.properties.title === sheetName)?.properties.sheetId || 0;
411
+
412
+ // Calculate text positions
413
+ const existingLength = existingScenario.length;
414
+ const newTextStartIndex = existingLength + 1; // +1 for newline
415
+
416
+ // Apply green color formatting to the new text
417
+ const batchUpdateRes = await sheets.spreadsheets.batchUpdate({
418
+ spreadsheetId,
419
+ resource: {
420
+ requests: [
421
+ {
422
+ repeatCell: {
423
+ range: {
424
+ sheetId: sheetId,
425
+ startRowIndex: rowNumber - 1,
426
+ endRowIndex: rowNumber,
427
+ startColumnIndex: columnLetter.charCodeAt(0) - 65,
428
+ endColumnIndex: columnLetter.charCodeAt(0) - 64
429
+ },
430
+ cell: {
431
+ userEnteredValue: {
432
+ stringValue: combinedContent
433
+ },
434
+ textFormatRuns: [
435
+ // Keep existing text in default color
436
+ {
437
+ startIndex: 0,
438
+ format: {}
439
+ },
440
+ // New text in green
441
+ {
442
+ startIndex: newTextStartIndex,
443
+ format: {
444
+ foregroundColor: {
445
+ red: 0.0,
446
+ green: 0.5,
447
+ blue: 0.0
448
+ }
449
+ }
450
+ }
451
+ ]
452
+ },
453
+ fields: 'userEnteredValue,textFormatRuns'
454
+ }
455
+ }
456
+ ]
457
+ }
458
+ });
459
+
460
+ if (batchUpdateRes.status === 200) {
461
+ return `Successfully appended updated scenario at ${cellReference}. New scenario added in green color: "${updatedScenario.substring(0, 100)}${updatedScenario.length > 100 ? '...' : ''}"`;
462
+ } else {
463
+ return `Failed to append scenario. Status: ${batchUpdateRes.status}`;
464
+ }
465
+
466
+ } catch (error) {
467
+ return `Error updating test scenario: ${error.message}`;
468
+ }
469
+ }
470
+ );
244
471
 
245
472
  tool("mobile_list_elements_on_screennn", "List elements on screen and their coordinates, with display text or accessibility label. Returns the complete XML structure to maintain hierarchy for XPath creation. Do not cache this result.", {}, async ({}) => {
246
473
  requireRobot();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nbakka/mcp-appium",
3
- "version": "3.0.22",
3
+ "version": "3.0.24",
4
4
  "description": "Appium MCP",
5
5
  "engines": {
6
6
  "node": ">=18"