mobile-wdio-kit 0.1.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.
Files changed (51) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +54 -0
  3. package/bin/cli.mjs +120 -0
  4. package/lib/create.mjs +128 -0
  5. package/lib/doctor-cli.mjs +38 -0
  6. package/lib/doctor.mjs +324 -0
  7. package/package.json +44 -0
  8. package/template/.cursor/mcp.json +13 -0
  9. package/template/.cursor/rules/wdio-mcp-mobile.mdc +52 -0
  10. package/template/.env.example +33 -0
  11. package/template/LICENSE +15 -0
  12. package/template/README.md +158 -0
  13. package/template/THIRD_PARTY.md +39 -0
  14. package/template/apps/.gitkeep +1 -0
  15. package/template/configs/wdio.cloud.android.conf.ts +31 -0
  16. package/template/configs/wdio.cloud.ios.conf.ts +31 -0
  17. package/template/configs/wdio.local.android.conf.ts +23 -0
  18. package/template/configs/wdio.local.ios.conf.ts +23 -0
  19. package/template/configs/wdio.shared.ts +36 -0
  20. package/template/package.json +61 -0
  21. package/template/patches/@wdio+mcp+3.2.2.patch +87 -0
  22. package/template/scripts/android-env.sh +102 -0
  23. package/template/scripts/doctor-cli.mjs +38 -0
  24. package/template/scripts/doctor-runner.mjs +7 -0
  25. package/template/scripts/download-demo-android.mjs +47 -0
  26. package/template/scripts/ensure-appium.mjs +71 -0
  27. package/template/scripts/mcp-with-appium.sh +30 -0
  28. package/template/scripts/mobile-wdio-doctor-core.mjs +324 -0
  29. package/template/scripts/ping-appium.mjs +24 -0
  30. package/template/scripts/run-android-local.sh +11 -0
  31. package/template/scripts/run-appium-local.sh +11 -0
  32. package/template/scripts/run-mcp-android-smoke.sh +47 -0
  33. package/template/src/env/buildEnv.test.ts +126 -0
  34. package/template/src/env/buildEnv.ts +81 -0
  35. package/template/src/env.ts +13 -0
  36. package/template/src/lib/safeFilePart.test.ts +17 -0
  37. package/template/src/lib/safeFilePart.ts +4 -0
  38. package/template/src/locators/locators.test.ts +35 -0
  39. package/template/src/locators/login.locators.ts +18 -0
  40. package/template/src/locators/nativeAlert.locators.ts +13 -0
  41. package/template/src/locators/tabBar.locators.ts +12 -0
  42. package/template/src/pages/Login.page.test.ts +73 -0
  43. package/template/src/pages/Login.page.ts +37 -0
  44. package/template/src/pages/NativeAlert.page.test.ts +91 -0
  45. package/template/src/pages/NativeAlert.page.ts +35 -0
  46. package/template/src/pages/TabBar.page.test.ts +35 -0
  47. package/template/src/pages/TabBar.page.ts +19 -0
  48. package/template/src/specs/app.login.spec.ts +20 -0
  49. package/template/src/test-utils/wdioTestGlobals.ts +82 -0
  50. package/template/tsconfig.json +22 -0
  51. package/template/vitest.config.ts +25 -0
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "mobile-wdio-kit",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold a production-ready WebdriverIO + Appium mobile framework with environment checks (doctor).",
5
+ "type": "module",
6
+ "license": "ISC",
7
+ "author": "Manoj Kumar Salugu",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Assign4/mobile-agentic-wdio-poc.git",
11
+ "directory": "packages/mobile-wdio-kit"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/Assign4/mobile-agentic-wdio-poc/issues"
15
+ },
16
+ "homepage": "https://github.com/Assign4/mobile-agentic-wdio-poc#readme",
17
+ "keywords": [
18
+ "webdriverio",
19
+ "appium",
20
+ "mobile",
21
+ "testing",
22
+ "android",
23
+ "ios",
24
+ "cursor",
25
+ "mcp"
26
+ ],
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "bin": {
31
+ "mobile-wdio-kit": "bin/cli.mjs",
32
+ "mwk": "bin/cli.mjs"
33
+ },
34
+ "files": [
35
+ "bin",
36
+ "lib",
37
+ "template",
38
+ "LICENSE",
39
+ "README.md"
40
+ ],
41
+ "scripts": {
42
+ "prepack": "node ./scripts/verify-template.mjs"
43
+ }
44
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "mcpServers": {
3
+ "wdio-mcp": {
4
+ "command": "sh",
5
+ "args": ["scripts/mcp-with-appium.sh"],
6
+ "cwd": "${workspaceFolder}",
7
+ "env": {
8
+ "ANDROID_HOME": "${env:HOME}/Library/Android/sdk",
9
+ "ANDROID_SDK_ROOT": "${env:HOME}/Library/Android/sdk"
10
+ }
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,52 @@
1
+ ---
2
+ description: Local Android/iOS automation with wdio-mcp — session start, natural phrasing, demo auto-login
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # wdio-mcp (local mobile)
7
+
8
+ The MCP server exposes tools with **fixed names** (for example `start_session`). You cannot rename the tool in Cursor; treat the user’s chat as **natural language** that you map to those tools.
9
+
10
+ This repo **patches `@wdio/mcp`** (`patches/@wdio+mcp+*.patch`, applied on `npm install`): for the **WebdriverIO demo APK** (`android.wdio.native.app` in `appPath`) and **iOS demo** (`ios-demo` in `appPath`), **`start_session` alone** completes login using `MOBILE_USERNAME` / `MOBILE_PASSWORD` (loaded via `scripts/mcp-with-appium.sh` sourcing `.env`). The session response text mentions demo auto-login when it succeeded.
11
+
12
+ ## Map user intent → `start_session`
13
+
14
+ Call **`start_session`** before any other wdio-mcp automation tool (`click_element`, `tap_element`, `get_elements`, etc.). There is no active driver until `start_session` succeeds.
15
+
16
+ When the user says any of the following (or similar), they mean **start a mobile session** — i.e. call **`start_session`** with the right platform and **absolute `appPath`**:
17
+
18
+ - “Start a session”, “start session”, “open a session”
19
+ - “Launch the app”, “open the app”, “start the app”, “run the app”
20
+ - “Open Android / the APK / the emulator app”
21
+ - “Launch iOS”, “open the **.app**”, “start on simulator”, “run the iOS build”
22
+
23
+ If they do not specify platform, prefer **Android** when the conversation is about this repo’s default demo APK; use **iOS** when they mention simulator, `.app`, or iOS explicitly.
24
+
25
+ ### Android (`platform: "android"`)
26
+
27
+ - `automationName`: `"UiAutomator2"`
28
+ - **`appPath`**: absolute path from repo root — read `ANDROID_APP_PATH` from `.env` and `resolve(projectRoot, ANDROID_APP_PATH)`, or default `apps/android.wdio.native.app.v2.0.0.apk`
29
+ - **`deviceName`**: match `adb devices` (e.g. `emulator-5554`) or `ANDROID_DEVICE_NAME` from `.env`
30
+ - **`appiumConfig`**: `{ "host": "<APPIUM_HOST>", "port": <APPIUM_PORT>, "path": "/" }` (defaults `127.0.0.1`, `4723`)
31
+ - `autoGrantPermissions`: `true` when launching the demo app (matches project smoke script)
32
+
33
+ ### iOS (`platform: "ios"`)
34
+
35
+ - `automationName`: `"XCUITest"`
36
+ - **`appPath`**: absolute path to the **`.app` bundle** — read `IOS_APP_PATH` from `.env` (e.g. `./apps/ios-demo.app`) and `resolve(projectRoot, IOS_APP_PATH)`. The user saying “launch the app” on iOS means this bundle path, not an APK.
37
+ - **`deviceName`**: `IOS_DEVICE_NAME` from `.env` or the simulator name the user specifies
38
+ - **`appiumConfig`**: same host/port/path pattern as Android when using local Appium
39
+
40
+ ## After `start_session` on the demo app
41
+
42
+ **Do not** run a separate login tool sequence unless the user opts out of built-in auto-login or the response says auto-login failed.
43
+
44
+ **Opt-out** (launch only, stay logged out): set `WDIO_MCP_DEMO_AUTO_LOGIN=0` (or `false`) in `.cursor/mcp.json` under `wdio-mcp` → `env`, reload the window / restart MCP, then `start_session` again. Or use a non-demo `appPath` (no built-in login). If the user says “only launch” or “don’t log in”, tell them to use that env flag (or a different app); do not assume you must drive the login UI yourself after a successful demo `start_session`.
45
+
46
+ For **non-demo** apps, there is no built-in login; drive the UI with `click_element` / `set_value` as needed.
47
+
48
+ Use `get_elements` when you need to see the current hierarchy.
49
+
50
+ ## Teardown
51
+
52
+ Call **`close_session`** when the user is done or asks to tear down.
@@ -0,0 +1,33 @@
1
+ ANDROID_APP_PATH=./apps/android.wdio.native.app.v2.0.0.apk
2
+ ANDROID_DEVICE_NAME="Android Emulator"
3
+ ANDROID_UDID=
4
+
5
+ IOS_APP_PATH=./apps/ios-demo.app
6
+ IOS_DEVICE_NAME="iPhone 15"
7
+ IOS_BUNDLE_ID=com.example.demo
8
+
9
+ APPIUM_HOST=127.0.0.1
10
+ APPIUM_PORT=4723
11
+
12
+ MOBILE_USERNAME=test@webdriver.io
13
+ MOBILE_PASSWORD=Test1234!
14
+
15
+ # Patched wdio-mcp (see patches/): set to 0 or false to skip built-in demo-app login after start_session.
16
+ # WDIO_MCP_DEMO_AUTO_LOGIN=0
17
+
18
+ ARTIFACTS_DIR=./artifacts
19
+
20
+ # LambdaTest-style cloud (optional)
21
+ CLOUD_USERNAME=
22
+ CLOUD_ACCESS_KEY=
23
+ CLOUD_HOSTNAME=mobile-hub.lambdatest.com
24
+ CLOUD_PATH=/wd/hub
25
+ BUILD_NAME=wdio-mobile
26
+
27
+ ANDROID_CLOUD_DEVICE="Galaxy S24"
28
+ ANDROID_CLOUD_PLATFORM_VERSION=14
29
+ ANDROID_CLOUD_APP=
30
+
31
+ IOS_CLOUD_DEVICE="iPhone 15"
32
+ IOS_CLOUD_PLATFORM_VERSION=17
33
+ IOS_CLOUD_APP=
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026, contributors
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,158 @@
1
+ # Mobile WDIO (minimal)
2
+
3
+ WebdriverIO **Mocha** specs for native **Android / iOS**, plus official **`@wdio/mcp`** so Cursor can drive the app from chat. No Cucumber and no custom MCP server—configs, page objects, locators, env, Vitest unit tests, and a small **`patch-package`** patch on `@wdio/mcp` so `start_session` can complete demo login in one step.
4
+
5
+ **Open source:** [LICENSE](./LICENSE) (ISC). **Trademarks, demo APK, npm deps, patches:** read [THIRD_PARTY.md](./THIRD_PARTY.md) before you publish or redistribute. **Publishing the CLI:** [RELEASING.md](./RELEASING.md).
6
+
7
+ ## Layout
8
+
9
+ ```text
10
+ configs/ wdio.local.*.conf.ts, wdio.cloud.*.conf.ts, wdio.shared.ts
11
+ src/env.ts dotenv-backed settings
12
+ src/specs/ *.spec.ts (Mocha)
13
+ src/pages/ small screen helpers
14
+ src/locators/ Android / iOS selectors
15
+ scripts/ android-env.sh, ensure-appium.mjs, mcp-with-appium.sh (Cursor MCP entry), ping-appium.mjs, run-mcp-android-smoke.sh
16
+ patches/ patch-package diff for @wdio/mcp (demo auto-login after start_session)
17
+ .cursor/rules/ wdio-mcp-mobile.mdc (agent: session + demo auto-login, Android/iOS app paths)
18
+ .cursor/mcp.json → `sh scripts/mcp-with-appium.sh` (do **not** use `npm run mcp:server` as the MCP command: npm prints to stdout and breaks JSON-RPC on stdio)
19
+ ```
20
+
21
+ ## Setup
22
+
23
+ 1. Copy `.env.example` → `.env`.
24
+ 2. Put apps under `apps/` or set `ANDROID_APP_PATH` / `IOS_APP_PATH`.
25
+ 3. Install drivers: `npm run appium:driver:android` (and iOS if needed).
26
+
27
+ ## Scaffold & environment check (`mobile-wdio-kit`)
28
+
29
+ The **`mobile-wdio-kit`** package (`packages/mobile-wdio-kit`) copies an embedded **template** of this repo, then runs **`npm install`**, **`patch-package`**, copies **`.env.example` → `.env`**, and downloads the **WebdriverIO demo Android APK** (APKs are not shipped inside the npm tarball).
30
+
31
+ **Where projects are created:** `create <path>` resolves the directory with Node’s `path.resolve()` from your **shell’s current working directory**—not from the global npm install location. So `mobile-wdio-kit create ~/Desktop/foo` creates on the Desktop regardless of whether you ran the command from `~/Projects` or elsewhere (and `~/...` works even inside quotes thanks to CLI tilde expansion).
32
+
33
+ ```bash
34
+ npx mobile-wdio-kit@latest create ./my-mobile-tests
35
+ cd my-mobile-tests
36
+ npm run doctor
37
+ ```
38
+
39
+ ```bash
40
+ npm install -g mobile-wdio-kit # after publish
41
+ mobile-wdio-kit create ./my-mobile-tests
42
+ ```
43
+
44
+ From a **git clone** of this repo (path must include `mobile-agentic-wdio-poc`):
45
+
46
+ ```bash
47
+ cd mobile-agentic-wdio-poc
48
+ npm install -g ./packages/mobile-wdio-kit
49
+ ```
50
+
51
+ **Doctor** checks Node, Android SDK / `adb`, demo APK, `.env`, Appium drivers, Xcode/simctl (macOS), and Appium `/status`. Here: `npm run doctor` or `npm run setup:demo-android` (APK only). Generated apps use a **vendored** doctor so `npm install` does not require this package on the registry.
52
+
53
+ Maintainers: `npm run kit:sync`, then publish—see [RELEASING.md](./RELEASING.md) and [packages/mobile-wdio-kit/README.md](./packages/mobile-wdio-kit/README.md).
54
+
55
+ ## CLI tests
56
+
57
+ ```bash
58
+ npm run test:android # ensures emulator if none online, then WDIO + Appium service
59
+ npm run test:ios # local Appium; you provide simulator/device
60
+ npm run test:cloud:android
61
+ npm run test:cloud:ios
62
+ ```
63
+
64
+ Demo credentials: `test@webdriver.io` / `Test1234!` (override with `MOBILE_USERNAME` / `MOBILE_PASSWORD`).
65
+
66
+ ## Cursor / MCP (prompt-driven testing)
67
+
68
+ With the default `.cursor/mcp.json`, **Cursor starts the MCP server via `scripts/mcp-with-appium.sh`**. That script starts **Appium in the background** if nothing is listening on `APPIUM_HOST` / `APPIUM_PORT` (logs under `artifacts/appium-mcp.log`), then runs `wdio-mcp`. If no device is online, it **starts an AVD in the background** but does not wait for a cold boot (Cursor’s MCP client times out if the launcher blocks for minutes). Prefer `adb devices` showing `device` before you enable the MCP server, or wait for the emulator to finish booting before calling `start_session`.
69
+
70
+ **Do not** set the MCP command to `npm run mcp:server`: npm echoes script lines to stdout, which corrupts the MCP protocol and produces errors like `Unexpected token '>'` / `is not valid JSON`.
71
+
72
+ To run Appium yourself (e.g. debugging), use `npm run appium:start` or `npx appium --address 127.0.0.1 --port 4723` as before.
73
+
74
+ ### 1. Emulator or device (when not using Cursor MCP)
75
+
76
+ ```bash
77
+ adb devices # should list one device/emulator as "device"
78
+ ```
79
+
80
+ The MCP launcher runs the same check and can boot an AVD if none is online (see `scripts/android-env.sh`).
81
+
82
+ ### 2. Confirm Appium is ready (optional)
83
+
84
+ ```bash
85
+ npm run mcp:ping-appium
86
+ ```
87
+
88
+ You should see `Appium ready at http://127.0.0.1:4723/status`.
89
+
90
+ ### 3. Cursor
91
+
92
+ 1. Open **this repo** as the workspace root (so `.cursor/mcp.json` `cwd` resolves correctly; if you use a multi-root workspace, set `cwd` in `.cursor/mcp.json` to the full path of this project).
93
+ 2. **Cursor Settings → MCP**: ensure **wdio-mcp** is enabled (toggle on). Reload the window after changing MCP config so the new launcher runs.
94
+ 3. Start an **Agent** chat with MCP allowed. The workspace rule **`.cursor/rules/wdio-mcp-mobile.mdc`** explains behavior. For this repo’s **WebdriverIO demo** Android APK / iOS `.app`, a **patched** `wdio-mcp` runs the login flow **inside** `start_session` (credentials from `.env`: `MOBILE_USERNAME` / `MOBILE_PASSWORD`; the launcher script sources `.env`). One prompt like “start session” is enough to be **logged in and past the success dialog**, ready to explore. To **skip** that behavior, set `WDIO_MCP_DEMO_AUTO_LOGIN=0` under `env` in `.cursor/mcp.json` and reload.
95
+
96
+ **Short example prompts**
97
+
98
+ ```text
99
+ Start an Android session (use .env for app path, device, Appium).
100
+ ```
101
+
102
+ ```text
103
+ Launch the demo app on the emulator — I want to explore after login.
104
+ ```
105
+
106
+ ```text
107
+ Start an iOS session with the .app under apps/ (resolve IOS_APP_PATH from .env), then close_session when done.
108
+ ```
109
+
110
+ **Explicit prompt (Android demo app)**
111
+
112
+ Use this when you want every capability spelled out. Replace `<ABSOLUTE_PATH_TO_REPO>` with the real path (e.g. from `pwd` in the project root). Match `deviceName` to `adb devices` (often `emulator-5554`).
113
+
114
+ ```text
115
+ Use wdio-mcp tools only. Call start_session for Android with:
116
+ - appPath: <ABSOLUTE_PATH_TO_REPO>/apps/android.wdio.native.app.v2.0.0.apk
117
+ - deviceName: emulator-5554
118
+ - automationName: UiAutomator2
119
+ - autoGrantPermissions: true
120
+ - appiumConfig: host 127.0.0.1, port 4723, path /
121
+
122
+ Demo login runs inside start_session; use get_elements to explore. close_session when done.
123
+ ```
124
+
125
+ **Explicit prompt (iOS simulator, `.app` bundle)**
126
+
127
+ Point `appPath` at the **directory** ending in `.app` (from `IOS_APP_PATH`, resolved to an absolute path). Set `platform: ios`, `automationName: XCUITest`, and `deviceName` to your simulator (e.g. from `IOS_DEVICE_NAME` in `.env`).
128
+
129
+ ```text
130
+ Use wdio-mcp only. start_session: platform ios, automationName XCUITest, appPath <ABSOLUTE_PATH_TO_REPO>/apps/ios-demo.app, deviceName iPhone 15, appiumConfig host 127.0.0.1 port 4723 path /. Demo login runs inside start_session; close_session when done.
131
+ ```
132
+
133
+ ### 4. Test the MCP stack without Cursor
134
+
135
+ This starts Appium, runs the official MCP client over stdio, drives the same login flow, then stops Appium:
136
+
137
+ ```bash
138
+ npm run test:mcp:android
139
+ ```
140
+
141
+ ### Manual server (debugging)
142
+
143
+ ```bash
144
+ npm run mcp:server:with-appium # same as Cursor: emulator + Appium + MCP
145
+ npm run mcp:server # MCP only; Appium must already be running
146
+ ```
147
+
148
+ Use these if you need to run the MCP server outside Cursor’s managed process.
149
+
150
+ ## Validate (no device)
151
+
152
+ ```bash
153
+ npm run validate
154
+ ```
155
+
156
+ ## Failure artifacts
157
+
158
+ On a failed spec, WDIO saves `artifacts/screenshots/<test-title>.png` (see `configs/wdio.shared.ts`).
@@ -0,0 +1,39 @@
1
+ # Third-party software, trademarks, and demo assets
2
+
3
+ This document is for **attribution and transparency**. It is **not legal advice**. If you ship a product or publish a package, have your own counsel review licenses and trademark use.
4
+
5
+ ## Trademarks
6
+
7
+ - **WebdriverIO**, **Appium**, and related marks belong to their respective owners (e.g. OpenJS Foundation / project communities).
8
+ - **Android** is a trademark of Google LLC.
9
+ - **Apple**, **iOS**, **Xcode**, and **Simulator** are trademarks of Apple Inc.
10
+ - **Cursor** is a trademark of Anysphere, Inc.
11
+ - **LambdaTest** is a trademark of LambdaTest Inc.
12
+
13
+ This project is **not affiliated with, endorsed by, or sponsored by** those organizations unless explicitly stated. Use their marks only in ways that comply with their brand guidelines.
14
+
15
+ ## Demo Android application (APK)
16
+
17
+ The optional setup script downloads the **WebdriverIO native demo app** binary from the upstream project (default URL is defined in `scripts/download-demo-android.mjs`). That app and its branding are subject to **their** license and terms—not this repo’s `LICENSE` file.
18
+
19
+ - Source / releases: [webdriverio/native-demo-app](https://github.com/webdriverio/native-demo-app)
20
+
21
+ Verify the license in that repository before redistributing the APK or using it in commercial contexts.
22
+
23
+ ## npm dependencies
24
+
25
+ Runtime and development dependencies (WebdriverIO, Appium, drivers, Vitest, `patch-package`, etc.) are each licensed under **their own** terms (MIT, Apache-2.0, and others). After `npm install`, see each package’s `LICENSE` or `package.json` `license` field, or run:
26
+
27
+ ```bash
28
+ npx license-checker --summary
29
+ ```
30
+
31
+ (Install `license-checker` globally or use another SBOM tool you prefer.)
32
+
33
+ ## Patch applied to `@wdio/mcp`
34
+
35
+ This repo includes a **`patch-package`** patch under `patches/` that modifies `@wdio/mcp` in `node_modules` after install. The **original `@wdio/mcp` code** remains under its upstream license; the patch is a derivative change applied on your machine at install time. Keep the patch file and this notice if you redistribute a project that relies on it.
36
+
37
+ ## `mobile-wdio-kit` npm package
38
+
39
+ The `packages/mobile-wdio-kit` package **bundles a template** copied from this repository (configs, scripts, tests, etc.) and **vendored doctor scripts**. Publishing that tarball does not transfer ownership of third-party code; attribution and upstream licenses still apply as above.
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,31 @@
1
+ import type { Capabilities, Options } from "@wdio/types";
2
+ import { env, requireCloudCredentials } from "../src/env.ts";
3
+ import { sharedMobileConfig } from "./wdio.shared.ts";
4
+
5
+ const { user, key } = requireCloudCredentials();
6
+
7
+ export const config: Options.Testrunner &
8
+ Capabilities.WithRequestedTestrunnerCapabilities = {
9
+ ...sharedMobileConfig,
10
+ user,
11
+ key,
12
+ protocol: "https",
13
+ hostname: env.cloudHostname,
14
+ port: 443,
15
+ path: env.cloudPath,
16
+ services: [],
17
+ capabilities: [
18
+ {
19
+ platformName: "Android",
20
+ "appium:automationName": "UiAutomator2",
21
+ "appium:deviceName": env.android.cloudDevice,
22
+ "appium:platformVersion": env.android.cloudPlatformVersion,
23
+ "appium:app": env.android.cloudApp,
24
+ "lt:options": {
25
+ build: `${env.buildName} — Android`,
26
+ name: "Mobile login",
27
+ isRealMobile: true,
28
+ },
29
+ },
30
+ ],
31
+ };
@@ -0,0 +1,31 @@
1
+ import type { Capabilities, Options } from "@wdio/types";
2
+ import { env, requireCloudCredentials } from "../src/env.ts";
3
+ import { sharedMobileConfig } from "./wdio.shared.ts";
4
+
5
+ const { user, key } = requireCloudCredentials();
6
+
7
+ export const config: Options.Testrunner &
8
+ Capabilities.WithRequestedTestrunnerCapabilities = {
9
+ ...sharedMobileConfig,
10
+ user,
11
+ key,
12
+ protocol: "https",
13
+ hostname: env.cloudHostname,
14
+ port: 443,
15
+ path: env.cloudPath,
16
+ services: [],
17
+ capabilities: [
18
+ {
19
+ platformName: "iOS",
20
+ "appium:automationName": "XCUITest",
21
+ "appium:deviceName": env.ios.cloudDevice,
22
+ "appium:platformVersion": env.ios.cloudPlatformVersion,
23
+ "appium:app": env.ios.cloudApp,
24
+ "lt:options": {
25
+ build: `${env.buildName} — iOS`,
26
+ name: "Mobile login",
27
+ isRealMobile: true,
28
+ },
29
+ },
30
+ ],
31
+ };
@@ -0,0 +1,23 @@
1
+ import type { Capabilities, Options } from "@wdio/types";
2
+ import { env } from "../src/env.ts";
3
+ import { sharedMobileConfig } from "./wdio.shared.ts";
4
+
5
+ export const config: Options.Testrunner &
6
+ Capabilities.WithRequestedTestrunnerCapabilities = {
7
+ ...sharedMobileConfig,
8
+ hostname: env.appiumHost,
9
+ port: env.appiumPort,
10
+ path: "/",
11
+ services: ["appium"],
12
+ capabilities: [
13
+ {
14
+ platformName: "Android",
15
+ "appium:automationName": "UiAutomator2",
16
+ "appium:deviceName": env.android.deviceName,
17
+ ...(env.android.udid ? { "appium:udid": env.android.udid } : {}),
18
+ "appium:app": env.android.appPath,
19
+ "appium:autoGrantPermissions": true,
20
+ "appium:noReset": false,
21
+ },
22
+ ],
23
+ };
@@ -0,0 +1,23 @@
1
+ import type { Capabilities, Options } from "@wdio/types";
2
+ import { env } from "../src/env.ts";
3
+ import { sharedMobileConfig } from "./wdio.shared.ts";
4
+
5
+ export const config: Options.Testrunner &
6
+ Capabilities.WithRequestedTestrunnerCapabilities = {
7
+ ...sharedMobileConfig,
8
+ hostname: env.appiumHost,
9
+ port: env.appiumPort,
10
+ path: "/",
11
+ services: ["appium"],
12
+ capabilities: [
13
+ {
14
+ platformName: "iOS",
15
+ "appium:automationName": "XCUITest",
16
+ "appium:deviceName": env.ios.deviceName,
17
+ "appium:app": env.ios.appPath,
18
+ "appium:bundleId": env.ios.bundleId,
19
+ "appium:autoAcceptAlerts": true,
20
+ "appium:noReset": false,
21
+ },
22
+ ],
23
+ };
@@ -0,0 +1,36 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import type { Options } from "@wdio/types";
5
+ import { env } from "../src/env.ts";
6
+ import { safeFilePart } from "../src/lib/safeFilePart.ts";
7
+
8
+ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
9
+ const specGlob = join(root, "src/specs/**/*.spec.ts");
10
+
11
+ /** Shared WDIO options for all mobile configs (Mocha + plain specs). */
12
+ export const sharedMobileConfig: Partial<Options.Testrunner> = {
13
+ runner: "local",
14
+ specs: [specGlob],
15
+ maxInstances: 1,
16
+ logLevel: "info",
17
+ waitforTimeout: 15_000,
18
+ connectionRetryTimeout: 120_000,
19
+ connectionRetryCount: 1,
20
+ framework: "mocha",
21
+ mochaOpts: { timeout: 90_000 },
22
+ reporters: ["spec"],
23
+ before: () => {
24
+ mkdirSync(join(env.artifactsDir, "screenshots"), { recursive: true });
25
+ },
26
+ afterTest: async (test, _ctx, { passed }) => {
27
+ if (passed) return;
28
+ const name = safeFilePart(test.title || "failed");
29
+ const path = join(env.artifactsDir, "screenshots", `${name}.png`);
30
+ try {
31
+ await browser.saveScreenshot(path);
32
+ } catch {
33
+ // Session or UiAutomator2 may already be dead (e.g. adb device offline).
34
+ }
35
+ },
36
+ };
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "mobile-agentic-wdio-poc",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "WebdriverIO + Appium mobile tests, Cursor MCP, Vitest (scaffolded via mobile-wdio-kit).",
7
+ "scripts": {
8
+ "postinstall": "patch-package",
9
+ "test:android": "sh scripts/run-android-local.sh",
10
+ "test:ios": "wdio run configs/wdio.local.ios.conf.ts",
11
+ "test:cloud:android": "wdio run configs/wdio.cloud.android.conf.ts",
12
+ "test:cloud:ios": "wdio run configs/wdio.cloud.ios.conf.ts",
13
+ "test:mcp:android": "sh scripts/run-mcp-android-smoke.sh",
14
+ "mcp:server": "wdio-mcp",
15
+ "mcp:server:with-appium": "MCP_BLOCK_UNTIL_EMULATOR_READY=1 sh scripts/mcp-with-appium.sh",
16
+ "mcp:ping-appium": "node scripts/ping-appium.mjs",
17
+ "appium:start": "sh scripts/run-appium-local.sh",
18
+ "appium:driver:android": "appium driver install --source=npm appium-uiautomator2-driver@4.2.9",
19
+ "appium:driver:ios": "appium driver install xcuitest",
20
+ "typecheck": "tsc --noEmit",
21
+ "test:unit": "vitest run",
22
+ "test:unit:watch": "vitest",
23
+ "test:unit:coverage": "vitest run --coverage",
24
+ "validate": "npm run typecheck && npm run test:unit:coverage",
25
+ "setup:demo-android": "node scripts/download-demo-android.mjs",
26
+ "doctor": "node scripts/doctor-runner.mjs --cwd ."
27
+ },
28
+ "keywords": [],
29
+ "author": "",
30
+ "license": "ISC",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/Assign4/mobile-agentic-wdio-poc.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/Assign4/mobile-agentic-wdio-poc/issues"
37
+ },
38
+ "homepage": "https://github.com/Assign4/mobile-agentic-wdio-poc#readme",
39
+ "devDependencies": {
40
+ "@modelcontextprotocol/sdk": "^1.27.1",
41
+ "@types/mocha": "^10.0.10",
42
+ "@types/node": "^25.5.2",
43
+ "@wdio/appium-service": "^9.27.0",
44
+ "@wdio/cli": "^9.27.0",
45
+ "@wdio/local-runner": "^9.27.0",
46
+ "@wdio/mcp": "^3.2.2",
47
+ "@wdio/mocha-framework": "^9.27.0",
48
+ "@wdio/spec-reporter": "^9.27.0",
49
+ "appium": "^2.19.0",
50
+ "appium-uiautomator2-driver": "^4.2.9",
51
+ "dotenv": "^17.4.0",
52
+ "expect-webdriverio": "^5.6.5",
53
+ "node-addon-api": "^8.7.0",
54
+ "node-gyp": "^12.2.0",
55
+ "patch-package": "^8.0.1",
56
+ "typescript": "^6.0.2",
57
+ "vitest": "^3.2.4",
58
+ "@vitest/coverage-v8": "^3.2.4",
59
+ "webdriverio": "^9.27.0"
60
+ }
61
+ }
@@ -0,0 +1,87 @@
1
+ diff --git a/node_modules/@wdio/mcp/lib/server.js b/node_modules/@wdio/mcp/lib/server.js
2
+ index 718f1b4..e9e2a00 100755
3
+ --- a/node_modules/@wdio/mcp/lib/server.js
4
+ +++ b/node_modules/@wdio/mcp/lib/server.js
5
+ @@ -3607,6 +3607,65 @@ Note: Unable to set window size (${windowWidth}x${windowHeight}). ${e}`;
6
+ }]
7
+ };
8
+ }
9
+ +async function wdioMcpAppendDemoAutoLoginNote(browser, platform2, appPath) {
10
+ + if (process.env.WDIO_MCP_DEMO_AUTO_LOGIN === "0" || process.env.WDIO_MCP_DEMO_AUTO_LOGIN === "false") {
11
+ + return "";
12
+ + }
13
+ + if (!appPath || typeof appPath !== "string") {
14
+ + return "";
15
+ + }
16
+ + const isAndroidDemo = platform2 === "android" && /android\.wdio\.native\.app/i.test(appPath);
17
+ + const isIosDemo = platform2 === "ios" && /ios-demo/i.test(appPath);
18
+ + if (!isAndroidDemo && !isIosDemo) {
19
+ + return "";
20
+ + }
21
+ + const waitMs = 25e3;
22
+ + const user = process.env.MOBILE_USERNAME || "test@webdriver.io";
23
+ + const pass = process.env.MOBILE_PASSWORD || "Test1234!";
24
+ + const clickSel = async (selector) => {
25
+ + await browser.waitUntil(browser.$(selector).isExisting, { timeout: waitMs });
26
+ + await browser.$(selector).scrollIntoView({ block: "center", inline: "center" });
27
+ + await browser.$(selector).click();
28
+ + };
29
+ + const setSel = async (selector, value) => {
30
+ + await browser.waitUntil(browser.$(selector).isExisting, { timeout: waitMs });
31
+ + await browser.$(selector).scrollIntoView({ block: "center", inline: "center" });
32
+ + await browser.$(selector).clearValue();
33
+ + await browser.$(selector).setValue(value);
34
+ + };
35
+ + try {
36
+ + await clickSel("~Login");
37
+ + await clickSel("~button-login-container");
38
+ + await setSel("~input-email", user);
39
+ + await setSel("~input-password", pass);
40
+ + try {
41
+ + await clickSel("~Login-screen");
42
+ + } catch {
43
+ + }
44
+ + await clickSel("~button-LOGIN");
45
+ + const deadline = Date.now() + 2e4;
46
+ + let src = "";
47
+ + while (Date.now() < deadline) {
48
+ + src = await browser.getPageSource();
49
+ + if (/success/i.test(src)) {
50
+ + break;
51
+ + }
52
+ + await new Promise((r) => setTimeout(r, 400));
53
+ + }
54
+ + if (!/success/i.test(src)) {
55
+ + throw new Error("success state not found after tapping LOGIN");
56
+ + }
57
+ + if (isAndroidDemo) {
58
+ + await clickSel('//android.widget.Button[@text="OK"]');
59
+ + } else {
60
+ + await clickSel("~OK");
61
+ + }
62
+ + return "\n\nDemo auto-login: finished (credentials from MOBILE_USERNAME / MOBILE_PASSWORD). Ready for exploration.";
63
+ + } catch (e) {
64
+ + return `
65
+ +Demo auto-login failed: ${e}. Session is still active; complete login manually if needed.`;
66
+ + }
67
+ +}
68
+ async function startMobileSession(args) {
69
+ const { platform: platform2, appPath, app, deviceName, noReset } = args;
70
+ if (!appPath && !app && noReset !== true) {
71
+ @@ -3640,6 +3699,7 @@ async function startMobileSession(args) {
72
+ appiumConfig: { hostname: serverConfig.hostname, port: serverConfig.port, path: serverConfig.path },
73
+ steps: []
74
+ });
75
+ + const demoLoginNote = await wdioMcpAppendDemoAutoLoginNote(browser, platform2, appPath);
76
+ const appInfo = appPath ? `
77
+ App: ${appPath}` : "\nApp: (connected to running app)";
78
+ const detachNote = shouldAutoDetach ? "\n\n(Auto-detach enabled: session will be preserved on close. Use close_session({ detach: false }) to force terminate.)" : "";
79
+ @@ -3649,7 +3709,7 @@ App: ${appPath}` : "\nApp: (connected to running app)";
80
+ type: "text",
81
+ text: `${platform2} app session started with sessionId: ${sessionId}
82
+ Device: ${deviceName}${appInfo}
83
+ -Appium Server: ${serverConfig.hostname}:${serverConfig.port}${serverConfig.path}${detachNote}`
84
+ +Appium Server: ${serverConfig.hostname}:${serverConfig.port}${serverConfig.path}${detachNote}${demoLoginNote}`
85
+ }
86
+ ]
87
+ };