# Use Appium MCP Without Confusing Exploration and Tests

> An agent that walks a mobile flow through Appium MCP has proved that one path existed once, which is not a regression test. This guide lists what appium-mcp 1.97.1 exposes (32 tools, with annotations that are per tool and only hints), builds a least-privilege exploration server with createAppiumMcpServer, a tool allowlist and a beforeCall guard that blocks uninstalls, data clears and model-supplied capabilities, and records every call to a JSONL transcript. It then turns an onboarding exploration into a review record and a WebdriverIO spec with explicit state setup, including why iOS needs launchApp rather than activateApp after clearApp.

- Author: [Shashank Rawlani](https://shashank.rawlani.com)

- Published: 2026-10-19T13:30:00.000Z

- Updated: 2026-10-19T13:30:00.000Z

- Category: AI in Testing

- Canonical source: https://automationtester.in/blog/ai-in-testing/appium-mcp-exploration-vs-test-suite

- Tags: appium, appium-mcp, mobile-testing, ai-agents, model-context-protocol, webdriverio, test-design

**Quick answer:** Use Appium MCP to explore a flow and to find out which identifiers the app exposes, never as the regression test itself. An agent picks its next tool call from what it sees, so two runs of the same prompt can take different paths, and a successful run proves only that one path existed once. Scope the server before the agent touches a device: hide the tools exploration does not need, block destructive actions per argument in a plugin hook (the protocol's tool annotations are per tool and are only hints), and write every call to a transcript. Then review the transcript, keep the identifiers that are stable, and write the test by hand with explicit state setup, explicit waits and assertions on outcomes. Link the transcript, the review decision and the test in one record.

An agent connected to an Android emulator through Appium MCP walks a new onboarding flow in four minutes: it finds the start button, types an email, accepts a notification prompt and lands on the home screen. Someone asks it to turn the run into a test, commits the result, and the test passes three times in CI. On the fourth run it fails at the email field. The emulator image had been reset, so the app opened on a language picker the agent never saw, and the "test" was a replay of the screens one device happened to show on one afternoon.

Nothing in that story is a bug in Appium MCP. The agent did what it was asked. The mistake was treating the record of an exploration as if it were a specification of behaviour. This last part of the series is about keeping the two apart: what the agent is good at, how to stop it doing damage while it works, and how its findings become a test that runs the same way every time.

**Versions checked on 2026-10-14.** `appium-mcp` 1.97.1 (npm `latest`, published 2026-10-12, requires Node.js 22 or newer), which bundles UiAutomator2 driver 8.7.0 and XCUITest driver 12.18.1. Appium 3.8.0, WebdriverIO 10.0.2 (released 2026-10-05) with expect-webdriverio 8.0.0, and the MCP specification revision 2026-07-28 with `@modelcontextprotocol/sdk` 1.32.1. The tool inventory and the guard probe below were run with Node.js 26.0.0 on macOS against the installed package, with no device attached. The WebdriverIO spec was syntax-checked but not run on a device for this article. Appium MCP releases often, so re-run the inventory script when you upgrade.

## A successful agent run proves one path, once

Appium MCP is listed on Appium's own tools page as an official tool maintained by the Appium team: a Model Context Protocol server that gives an AI client tools to start sessions, find elements, tap, type, read the page source and generate locators. It does not even need an Appium server for local work. When a session is created for `android` or `ios` without a `remoteServerUrl`, it drives its bundled UiAutomator2 or XCUITest driver directly, so you only need the platform toolchain.

What it cannot give you is repeatability. The model chooses each call from the latest screenshot or page source, the prompt, the tool descriptions and its own weights. Change any of those, including a silent model update on the client side, and the path changes. A test that is allowed to choose its own path is not checking anything you wrote down.

The package is candid about this. Its `appium_generate_tests` tool does not generate code. In 1.97.1 it returns a block of instructions telling the model to execute every step on a live session first and to "never generate from the scenario alone", then to write the test file itself. The code you get is whatever the model writes from its own transcript, and its quality depends on the model, not on Appium.

## What the server exposes by default

Before deciding what an exploration agent may do, list what it can do. This script connects to the server over stdio with the official MCP SDK and prints every tool with its annotations:

```
// list-tools.mjs: node list-tools.mjs npx appium-mcp@1.97.1
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';

const [cmd, ...args] = process.argv.slice(2);
const transport = new StdioClientTransport({
  command: cmd,
  args,
  env: { ...process.env, NO_UI: 'true' },
  stderr: 'ignore',
});
const client = new Client({ name: 'tool-audit', version: '1.0.0' });
await client.connect(transport);
const { tools } = await client.listTools();
for (const t of tools) {
  const a = t.annotations ?? {};
  console.log(
    t.name.padEnd(30),
    `readOnly=${a.readOnlyHint ?? '-'}`,
    `destructive=${a.destructiveHint ?? '-'}`,
    `openWorld=${a.openWorldHint ?? '-'}`,
  );
}
console.log('total', tools.length);
await client.close();
```

Against 1.97.1 with no optional features enabled it reports 32 tools. An excerpt:

```
appium_session_management      readOnly=false destructive=true openWorld=false
appium_find_element            readOnly=true destructive=- openWorld=false
appium_get_page_source         readOnly=false destructive=- openWorld=false
appium_app_lifecycle           readOnly=false destructive=- openWorld=true
appium_mobile_permissions      readOnly=false destructive=- openWorld=false
appium_mobile_file             readOnly=false destructive=- openWorld=false
generate_locators              readOnly=true destructive=- openWorld=false
total 32
```

Two details matter. First, the specification says clients must treat annotations as untrusted unless they come from trusted servers, and the schema calls every annotation a hint. Second, annotations describe a tool, not a call. `appium_app_lifecycle` is one tool with ten actions: `query_state` and `is_installed` sit beside `uninstall`, `install` and `clear`. Its unset `destructiveHint` defaults to `true` under the schema, which is honest but blunt: a client that asks "allow this tool?" can only approve or refuse all ten actions together. In a client that remembers approvals per tool, allowing the agent once to check whether the app is running also lets it wipe the app's data.

## A least-privilege exploration server

Appium MCP exports a composition API, `createAppiumMcpServer` from `appium-mcp/core`, with two controls you need. A `policy` with `allowTools` hides every tool whose name does not match, at registration time, so the model never sees it. A plugin's `beforeCall` hook runs before each tool and can return a result to short-circuit it, which is where per-argument rules go. The same hook pair is a natural place to write the transcript.

```
// explore-server.mjs: an Appium MCP server scoped for exploration sessions
import { appendFileSync } from 'node:fs';
import { createAppiumMcpServer } from 'appium-mcp/core';

const TRANSCRIPT = process.env.EXPLORE_TRANSCRIPT ?? 'exploration.jsonl';
const SAFE_APP_ACTIONS = new Set(['activate', 'terminate', 'query_state', 'is_installed', 'deep_link']);

function record(entry) {
  appendFileSync(TRANSCRIPT, JSON.stringify({ at: new Date().toISOString(), ...entry }) + '\n');
}

function denyReason(toolName, args) {
  if (toolName === 'appium_app_lifecycle' && !SAFE_APP_ACTIONS.has(args.action)) {
    return `appium_app_lifecycle action=${args.action} needs a human`;
  }
  if (toolName === 'appium_session_management' && args.action === 'create' && args.capabilities) {
    return 'capabilities come from CAPABILITIES_CONFIG, not from the model';
  }
  return null;
}

class ExplorationGuard {
  name = 'acme-exploration-guard';
  version = '1.0.0';

  register() {}

  async beforeCall({ toolName, args }) {
    const reason = denyReason(toolName, args);
    record({ tool: toolName, args, decision: reason ? 'blocked' : 'allowed' });
    if (reason) {
      return { isError: true, content: [{ type: 'text', text: `Blocked: ${reason}. Ask the operator.` }] };
    }
  }

  async afterCall({ toolName }, result) {
    const text = result.content.find((c) => c.type === 'text')?.text ?? '';
    const evidence = result.content
      .filter((c) => c.type === 'resource' && c.resource.mimeType === 'application/vnd.appium.evidence+json')
      .map((c) => JSON.parse(c.resource.text));
    record({ tool: toolName, isError: Boolean(result.isError), result: text.slice(0, 300), evidence });
  }
}

const server = await createAppiumMcpServer({
  plugins: [new ExplorationGuard()],
  policy: {
    allowTools: [
      /^select_device$/,
      /^appium_session_management$/,
      /^appium_app_lifecycle$/,
      /^appium_(find_element|find_elements|get_text|get_element_attribute|get_page_source|screenshot|get_window_size)$/,
      /^appium_(gesture|set_value|mobile_keyboard|alert)$/,
      /^generate_locators$/,
    ],
  },
});

await server.start({ transportType: 'stdio' });
```

The capabilities rule is there because of how session creation merges its inputs. In 1.97.1, capabilities passed in the tool call are spread over the ones loaded from `CAPABILITIES_CONFIG`, so a model that decides a clean start would help can send `appium:fullReset`, which the UiAutomator2 driver documents as uninstalling the app before the session. Blocking the lifecycle tool's `uninstall` action is pointless if the same effect is one capability away.

A probe client that lists the tools and then tries four calls against this server, still with no device attached, prints:

```
15 tools: select_device, appium_session_management, appium_gesture, appium_find_element, appium_find_elements, appium_set_value, appium_mobile_keyboard, appium_get_text, appium_get_element_attribute, appium_get_page_source, appium_alert, appium_screenshot, appium_get_window_size, appium_app_lifecycle, generate_locators
uninstall -> true Blocked: appium_app_lifecycle action=uninstall needs a human. Ask the operator.
permissions -> MCP error -32602: MCP error -32602: Unknown tool: appium_mobile_permissions
create -> true Blocked: capabilities come from CAPABILITIES_CONFIG, not from the model. Ask the operator.
find -> true No active driver session. Use appium_session_management (action=create or action=attach), or pass a valid sessionId.
```

Permission changes are not hidden because they are dangerous on their own. They are hidden because an exploration that grants itself the camera has explored a state real users do not start in; the [permissions part](/blog/automation-tutorials/mobile-permission-testing-system-ui) of this series seeds those states deliberately instead. One behaviour observed in the probe: when `beforeCall` short-circuits a call, `afterCall` does not run, which is why the guard records the decision before returning.

The client configuration then points at this file instead of `npx appium-mcp@latest`, the line Appium's tools page and the README both show. `@latest` resolves again on every start, which is the same drift the [drivers and plugins part](/blog/automation-tutorials/appium-drivers-plugins-test-boundaries) pinned away for the Appium server. Put `"appium-mcp": "1.97.1"` in the package next to the script, with a lockfile, and set the remaining controls as environment variables:

```
{
  "mcpServers": {
    "appium-explore": {
      "type": "stdio",
      "command": "node",
      "args": ["/path/to/repo/tools/appium-explore/explore-server.mjs"],
      "env": {
        "ANDROID_HOME": "/path/to/android/sdk",
        "CAPABILITIES_CONFIG": "/path/to/repo/tools/appium-explore/capabilities.json",
        "EXPLORE_TRANSCRIPT": "/path/to/repo/exploration/onboarding.jsonl",
        "APPIUM_MCP_EVIDENCE": "true",
        "ALLOW_REMOTE_APP_URLS": "false",
        "REMOTE_SERVER_URL_ALLOW_REGEX": "^http://127\\.0\\.0\\.1:4723/?$"
      }
    }
  }
}
```

`APPIUM_MCP_EVIDENCE` attaches a structured record (locator, resolved element id, context, timing and a normalized error code such as `ELEMENT_NOT_FOUND` or `STALE_ELEMENT`) to find and gesture responses, and the guard copies it into the transcript. `ALLOW_REMOTE_APP_URLS=false` stops the agent installing a build from an HTTP URL, and the allow regex limits which Appium server it may connect to. The README is explicit that the server is meant to run for a single local user or inside a trusted CI job and must not be exposed to untrusted clients.

The policy cannot fix the device itself. Embedded drivers act with your user's `adb` and Xcode access, so explore on a dedicated emulator or simulator, never on a phone signed in to anyone's real accounts.

## Write the stopping point into the prompt

An unbounded prompt ("explore the app") produces a long transcript and no decision. Give the agent one flow, the data it may type, a place to stop and the output you want. For onboarding:

```
Goal: walk onboarding on the running Android emulator, from the first
screen after a fresh install to the home screen, once with a malformed
email ("ana@example") and once with the address in ONBOARDING_TEST_EMAIL.

Rules:
- Use appium_find_element with accessibility id or id first. Note every
  element you could only reach by xpath, text or coordinates.
- Do not sign in to any other account and do not type anything that is
  not given above.
- Stop at the home screen, or after 40 tool calls, whichever comes first.

Report: for each screen, the identifiers you used, the ones missing,
and anything that appeared on one run but not the other.
```

The last line is the useful part. You are not asking for a test. You are asking what the app exposes, and where it differs between runs, because those differences are exactly what a deterministic test has to control.

## From transcript to reviewed decision

Read the JSONL with the report beside it. For each element the agent touched, decide whether the locator can go into a test. The rules are the ones the Appium MCP README gives for `appium_find_element`: accessibility id first, then id, then platform-native queries, and XPath "last resort". Coordinates and vision results from the optional `appium_ai` tool never go into a test. That tool is not registered unless `AI_VISION_ENABLED=true` is set, and the README says it is gated so the model does not reach for a slow, paid vision call when a stable locator would do.

Write the outcome down, next to the transcript, in a form a reviewer can disagree with:

```
{
  "flow": "onboarding-email",
  "transcript": "exploration/onboarding.jsonl",
  "server": "appium-mcp 1.97.1 via tools/appium-explore",
  "accepted": {
    "onboarding-start": "resource-id and accessibilityIdentifier on both platforms",
    "onboarding-email": "same on both platforms",
    "home-root": "same on both platforms"
  },
  "rejected": {
    "//*[@text='Continue']": "visible text; changes with locale",
    "tap at 540,1680": "coordinates from one screen size"
  },
  "appRequests": ["add testTag/accessibilityIdentifier onboarding-email-continue and onboarding-email-error"],
  "notInTest": ["language picker appeared on run 2 only: preset locale in capabilities instead"],
  "test": "test/specs/onboarding.e2e.js",
  "reviewedBy": "maintainer handle"
}
```

The `appRequests` line is often the most valuable output of the whole exercise. An agent that had to fall back to text or coordinates has found a gap in the app's test hooks, and the fix belongs in the app code. If `id` lookups miss on a Compose screen, the [selector debugging part](/blog/automation-tutorials/debug-appium-selectors-android-ios) covers how test tags reach the hierarchy.

## Replaying the transcript versus writing the test

A test written by copying the agent's calls in order looks like this:

```
it('onboarding works', async () => {
  await $('//*[@text="Get started"]').click();
  await driver.pause(2000);
  await $('android=new UiSelector().className("android.widget.EditText")').setValue('qa.agent+0412@example.com');
  await driver.action('pointer').move({ x: 540, y: 1680 }).down().up().perform();
  await driver.pause(3000);
  await expect($('//*[@text="Welcome, qa"]')).toBeDisplayed();
});
```

Every line carries something from the afternoon it was recorded. It assumes the device was already past the language picker. It relies on English button text and on a screen where the continue button sat at that exact pixel. The address worked because no account existed for it yet, so the second run hits "already registered". The fixed pauses were how long things took that day, and the assertion checks the greeting the agent happened to see rather than the thing onboarding is for. The malformed-email case, which the agent did explore, is missing entirely.

The authored version owns its starting state and asserts outcomes through identifiers the review accepted:

```
import { expect } from '@wdio/globals';

const APP_ID = 'com.example.onboarding';
const byTestId = (id) => (driver.isAndroid ? `id=${id}` : `~${id}`);

async function startFromFreshInstallState() {
  if (driver.isAndroid) {
    // pm clear expects the app to be stopped first
    await driver.execute('mobile: terminateApp', { appId: APP_ID });
    await driver.execute('mobile: clearApp', { appId: APP_ID });
    await driver.execute('mobile: activateApp', { appId: APP_ID });
  } else {
    // simulator only; clearApp terminates the app, and activateApp would throw on a stopped app
    await driver.execute('mobile: clearApp', { bundleId: APP_ID });
    await driver.execute('mobile: launchApp', { bundleId: APP_ID });
  }
}

async function submitEmail(address) {
  await $(byTestId('onboarding-start')).click();
  const email = $(byTestId('onboarding-email'));
  await email.waitForDisplayed();
  await email.setValue(address);
  await $(byTestId('onboarding-email-continue')).click();
}

describe('onboarding email step', () => {
  beforeEach(startFromFreshInstallState);

  it('keeps the user on the email step when the address is malformed', async () => {
    await submitEmail('ana@example');

    await expect($(byTestId('onboarding-email-error'))).toBeDisplayed();
    await expect($(byTestId('onboarding-email'))).toBeDisplayed();
    await expect($(byTestId('home-root'))).not.toBeExisting();
  });

  it('reaches home with a valid address', async () => {
    await submitEmail(process.env.ONBOARDING_TEST_EMAIL);

    await expect($(byTestId('home-root'))).toBeDisplayed();
  });
});
```

The platform branches are not decoration. The UiAutomator2 driver says `mobile: clearApp` runs `pm clear` and that the app "should not be running", hence the terminate first. The XCUITest driver's `mobile: clearApp` terminates the app itself, works only on simulators and throws on real devices, where Apple's only fully supported reset is uninstall and reinstall. Its `mobile: activateApp` throws if the app is not running, so the iOS branch uses `mobile: launchApp`. An agent replaying its own calls would never discover that, because it never started from a stopped app.

The valid-address case reads its address from the environment because a reused address fails as "already registered" on the second run. Give each run a fresh account from your backend's test API, or reset the account between runs. Either way that is setup code you own, not something the agent improvises.

### Which assertions stay on the device

The error test checks that an error is shown and that the user cannot move on. It does not check the error's wording or the rules for a valid address. Those belong in a unit test on the validator, where a table of fifty addresses runs in milliseconds; Android's testing guidance puts unit tests on the host and keeps the slow application-level tests for behaviour that needs the whole binary. The [first part](/blog/automation-tutorials/mobile-test-strategy-fidelity-cost) of this series makes that placement argument in full. The agent found the error state. Deciding which layer proves it is the maintainer's job.

## The price of keeping exploration separate

You now maintain three things where the demo promised one: the guarded server, the review records and the suite. Review takes a person's time on every flow, and a transcript from a model you no longer use is only useful for the decisions it led to, so keep the review record and treat the JSONL as disposable. Exploration also costs tokens, and full page-source responses are large; the README's own measurement for a synthetic 95,000-character page source is a result of about 95 KB even in its leaner viewer mode.

Skip the agent for screens that already have identifiers and a page object. Exploring them again tells you nothing a page-source dump would not. The agent earns its cost on a new or redesigned flow, on a screen nobody on the team has automated, or when you need a quick inventory of missing test hooks before a release.

## Explore one flow under guard this week

Pick the newest flow in your app that has no automated test. Install `appium-mcp@1.97.1` into a small tools package, run the inventory script against it and save the output, so the next upgrade shows you which tools appeared. Start the guarded server against a dedicated emulator, give the agent a bounded prompt with a stop condition, and let it run. Then write the review record and one authored test from it, and open both in the same pull request so the reviewer sees the transcript, the decisions and the test together.

The evidence to capture is that pull request: a JSONL transcript with every blocked call visible, a review record that names each accepted and rejected locator and each request to the app team, and a spec that passes twice in a row on a freshly reset emulator.

## Questions about Appium MCP and test suites

### Can the agent itself run in CI as the regression check?

It can run there; the README describes a trusted CI job as a supported setting and offers `NO_UI` for headless use. As a gate, it cannot be trusted. A pass means the model found some route to the goal on that run, possibly by a route your users cannot take, and a failure might be the model, not the app. Use agent runs in CI for triage or for inventorying a new build, and keep the merge decision on the authored suite.

### Does Appium MCP need a running Appium server?

Not for local Android and iOS sessions. Without `remoteServerUrl`, it uses its bundled UiAutomator2 and XCUITest drivers, and you only need `ANDROID_HOME`, the Android SDK tools or Xcode. With `remoteServerUrl`, the remote server's installed drivers and plugins apply, which is the mode to use for a device farm.

### Should the exploration server enable the vision tool?

Only to answer "is there any way to reach this element at all", and only on a flow you are about to give proper identifiers. `appium_ai` returns coordinates encoded in an id, not a WebDriver element, and it sends screenshots to whichever vision API you configure. Neither belongs in a test, and the screenshots may contain data you would not send to a third party.

## Primary references

- [Appium ecosystem tools page](https://appium.io/docs/en/latest/ecosystem/tools/): Appium MCP listed under tools maintained by the Appium team, with the `npx appium-mcp@latest` client configuration.

- [appium-mcp README at v1.97.1](https://github.com/appium/appium-mcp/blob/v1.97.1/README.md): embedded versus remote driver modes, the environment variables (`APPIUM_MCP_EVIDENCE`, `ALLOW_REMOTE_APP_URLS`, `REMOTE_SERVER_URL_ALLOW_REGEX`, `AI_VISION_ENABLED`, `NO_UI`), the trust model, `createAppiumMcpServer` with `policy.allowTools`, plugin hooks, the locator priority and the page-source size measurement.

- [appium-mcp source: generate-tests.ts at v1.97.1](https://github.com/appium/appium-mcp/blob/v1.97.1/src/tools/test-generation/generate-tests.ts): that `appium_generate_tests` returns instructions for the model rather than generated code.

- [appium-mcp source: app.ts at v1.97.1](https://github.com/appium/appium-mcp/blob/v1.97.1/src/tools/app-management/app.ts): the ten actions of `appium_app_lifecycle` under one tool and its annotations.

- [appium-mcp source: create-session.ts at v1.97.1](https://github.com/appium/appium-mcp/blob/v1.97.1/src/tools/session/create-session.ts): capabilities from the tool call spread over those loaded from `CAPABILITIES_CONFIG`.

- [appium-mcp source: evidence.ts at v1.97.1](https://github.com/appium/appium-mcp/blob/v1.97.1/src/tools/evidence.ts): the evidence record fields and the normalized error codes.

- [npm registry entry for appium-mcp](https://www.npmjs.com/package/appium-mcp): version 1.97.1 as `latest` and the Node.js 22 engine requirement.

- [MCP specification 2026-07-28, Tools](https://modelcontextprotocol.io/specification/2026-07-28/server/tools): annotations must be treated as untrusted unless from trusted servers, and a human should be able to deny tool invocations.

- [MCP specification 2026-07-28, Schema reference](https://modelcontextprotocol.io/specification/2026-07-28/schema): `ToolAnnotations` are hints, with `readOnlyHint` defaulting to false and `destructiveHint` to true.

- [The v8.7.0 UiAutomator2 driver README](https://github.com/appium/appium-uiautomator2-driver/blob/v8.7.0/README.md): `mobile: clearApp` calling `pm clear` on a stopped app, `mobile: activateApp`, and `appium:fullReset` uninstalling before a session.

- [XCUITest driver execute methods](https://appium.github.io/appium-xcuitest-driver/latest/reference/execute-methods/): `mobile: clearApp` being simulator-only, `mobile: activateApp` throwing on an app that is not running, and `mobile: launchApp`.

- [WebdriverIO selector guide](https://webdriver.io/docs/selectors/): the `~` accessibility id selector and its platform mapping.

- [WebdriverIO v10.0.0 release notes](https://github.com/webdriverio/webdriverio/releases/tag/v10.0.0): the 2026-10-05 major release and its breaking changes, including that WebdriverIO 10 requires Appium 3.

- [Android Developers: testing strategies](https://developer.android.com/training/testing/fundamentals/strategies): unit tests on the host versus application tests on a debuggable binary.

## Continue reading on AutomationTester.in

- [Playwright MCP Is Not Your Regression Test Suite](https://automationtester.in/blog/ai-in-testing/playwright-mcp-vs-regression-test-suite)
- [How to Test an MCP Server: Protocol to Safety](https://automationtester.in/blog/ai-in-testing/how-to-test-mcp-server-protocol-auth-safety)
- [Validate AI Agent Tool Calls in TypeScript: Schema, Authorization, Approval](https://automationtester.in/blog/ai-in-testing/validate-ai-agent-tool-calls-typescript)
- [MCP Observability: Audit Tool Calls Without Logging Secrets](https://automationtester.in/blog/ai-in-testing/mcp-observability-audit-without-secrets)
- [Test MCP Hosts for Prompt Injection and Data Exfiltration](https://automationtester.in/blog/ai-in-testing/test-mcp-prompt-injection-data-exfiltration)

Source: [Use Appium MCP Without Confusing Exploration and Tests](https://automationtester.in/blog/ai-in-testing/appium-mcp-exploration-vs-test-suite) by Shashank Rawlani.
