@wix-pilot/webdriverio-appium 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -0
- package/dist/examples/example.test.js +6 -8
- package/dist/index.d.ts +8 -2
- package/dist/index.js +24 -73
- package/dist/utils/getStableViewHierarchy.d.ts +3 -0
- package/dist/utils/getStableViewHierarchy.js +37 -0
- package/package.json +6 -11
- package/dist/wdio.conf.d.ts +0 -28
- package/dist/wdio.conf.js +0 -34
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# @wix-pilot/appium 📱
|
|
2
|
+
|
|
3
|
+
> Appium driver for Wix Pilot - Cross-platform mobile testing with natural language
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `@wix-pilot/webdriverio-appium` package provides Appium integration for Wix Pilot, enabling:
|
|
8
|
+
- Native iOS and Android app testing
|
|
9
|
+
- Hybrid app testing
|
|
10
|
+
- Mobile web testing
|
|
11
|
+
- Cross-platform test automation
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# npm
|
|
17
|
+
npm install --save-dev @wix-pilot/webdriverio-appium webdriverio @wix-pilot/core
|
|
18
|
+
|
|
19
|
+
# yarn
|
|
20
|
+
yarn add -D @wix-pilot/webdriverio-appium webdriverio @wix-pilot/core
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### 1. Set up your LLM Handler
|
|
26
|
+
```typescript
|
|
27
|
+
import { PromptHandler } from '@wix-pilot/core';
|
|
28
|
+
|
|
29
|
+
class CustomPromptHandler implements PromptHandler {
|
|
30
|
+
async runPrompt(prompt: string, image?: string): Promise<string> {
|
|
31
|
+
// Integrate with your preferred LLM service
|
|
32
|
+
const response = await yourLLMService.complete({
|
|
33
|
+
prompt,
|
|
34
|
+
imageUrl: image, // Optional: for visual testing support
|
|
35
|
+
});
|
|
36
|
+
return response.text;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
isSnapshotImageSupported(): boolean {
|
|
40
|
+
return true; // Set to true if your LLM supports image analysis
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 2. Create and Run Tests
|
|
46
|
+
```typescript
|
|
47
|
+
import pilot from '@wix-pilot/core';
|
|
48
|
+
import { WebdriverIOAppiumFrameworkDriver } from '@wix-pilot/webdriverio-appium';
|
|
49
|
+
|
|
50
|
+
describe('Mobile App Testing', () => {
|
|
51
|
+
beforeAll(async () => {
|
|
52
|
+
pilot.init({
|
|
53
|
+
driver: new WebdriverIOAppiumFrameworkDriver(),
|
|
54
|
+
promptHandler: new CustomPromptHandler(),
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
beforeEach(() => pilot.start());
|
|
59
|
+
afterEach(() => pilot.end());
|
|
60
|
+
|
|
61
|
+
it('should handle shopping cart flow', async () => {
|
|
62
|
+
await pilot.perform([
|
|
63
|
+
'Launch the app',
|
|
64
|
+
'Allow any permission prompts',
|
|
65
|
+
'Tap the "Products" tab',
|
|
66
|
+
'Scroll to "Wireless Headphones"',
|
|
67
|
+
'Add item to cart',
|
|
68
|
+
'The cart badge should show "1"'
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Related Packages
|
|
75
|
+
|
|
76
|
+
- [@wix-pilot/core](../../core) - Core Wix Pilot engine
|
|
77
|
+
- [@wix-pilot/detox](../detox) - Alternative React Native testing driver
|
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const core_1 =
|
|
3
|
+
const core_1 = require("@wix-pilot/core");
|
|
7
4
|
const promptHandler_1 = require("../utils/promptHandler");
|
|
8
5
|
const index_1 = require("../index");
|
|
9
6
|
describe("Example Test Suite", () => {
|
|
10
7
|
let frameworkDriver;
|
|
8
|
+
let pilot;
|
|
11
9
|
before(async () => {
|
|
12
10
|
const promptHandler = new promptHandler_1.PromptHandler();
|
|
13
11
|
frameworkDriver = new index_1.WebdriverIOAppiumFrameworkDriver();
|
|
14
|
-
core_1.
|
|
12
|
+
pilot = new core_1.Pilot({
|
|
15
13
|
frameworkDriver,
|
|
16
14
|
promptHandler,
|
|
17
15
|
});
|
|
18
16
|
});
|
|
19
17
|
beforeEach(async () => {
|
|
20
|
-
|
|
18
|
+
pilot.start();
|
|
21
19
|
});
|
|
22
20
|
afterEach(async () => {
|
|
23
|
-
|
|
21
|
+
pilot.end();
|
|
24
22
|
});
|
|
25
23
|
it("perform test with pilot", async () => {
|
|
26
|
-
await
|
|
24
|
+
await pilot.autopilot("earn 2 points in the game");
|
|
27
25
|
});
|
|
28
26
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { TestingFrameworkAPICatalog, TestingFrameworkDriver } from "@wix-pilot/core";
|
|
1
|
+
import { TestingFrameworkAPICatalog, TestingFrameworkDriver, TestingFrameworkDriverConfig } from "@wix-pilot/core";
|
|
2
2
|
export declare class WebdriverIOAppiumFrameworkDriver implements TestingFrameworkDriver {
|
|
3
3
|
constructor();
|
|
4
|
+
/**
|
|
5
|
+
* Additional driver configuration.
|
|
6
|
+
*
|
|
7
|
+
* @property useSnapshotStabilitySync - Indicates whether the driver should use wait for screen stability.
|
|
8
|
+
*/
|
|
9
|
+
get driverConfig(): TestingFrameworkDriverConfig;
|
|
4
10
|
/**
|
|
5
11
|
* Attempts to capture the current view hierarchy (source) of the mobile app as XML.
|
|
6
12
|
* If there's no active session or the app isn't running, returns an error message.
|
|
@@ -10,7 +16,7 @@ export declare class WebdriverIOAppiumFrameworkDriver implements TestingFramewor
|
|
|
10
16
|
* Captures a screenshot of the current device screen and saves it to a temp directory.
|
|
11
17
|
* Returns the path to the saved screenshot if successful, or undefined otherwise.
|
|
12
18
|
*/
|
|
13
|
-
captureSnapshotImage(): Promise<string | undefined>;
|
|
19
|
+
captureSnapshotImage(_: boolean): Promise<string | undefined>;
|
|
14
20
|
/**
|
|
15
21
|
* Returns the API catalog describing the testing capabilities
|
|
16
22
|
* (matchers, actions, assertions, device/system APIs, etc.)
|
package/dist/index.js
CHANGED
|
@@ -32,22 +32,33 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.WebdriverIOAppiumFrameworkDriver = void 0;
|
|
37
40
|
const fs = __importStar(require("fs"));
|
|
38
41
|
const path = __importStar(require("path"));
|
|
42
|
+
const getStableViewHierarchy_1 = require("./utils/getStableViewHierarchy");
|
|
43
|
+
const os_1 = __importDefault(require("os"));
|
|
39
44
|
class WebdriverIOAppiumFrameworkDriver {
|
|
40
45
|
constructor() { }
|
|
46
|
+
/**
|
|
47
|
+
* Additional driver configuration.
|
|
48
|
+
*
|
|
49
|
+
* @property useSnapshotStabilitySync - Indicates whether the driver should use wait for screen stability.
|
|
50
|
+
*/
|
|
51
|
+
get driverConfig() {
|
|
52
|
+
return { useSnapshotStabilitySync: true };
|
|
53
|
+
}
|
|
41
54
|
/**
|
|
42
55
|
* Attempts to capture the current view hierarchy (source) of the mobile app as XML.
|
|
43
56
|
* If there's no active session or the app isn't running, returns an error message.
|
|
44
57
|
*/
|
|
45
58
|
async captureViewHierarchyString() {
|
|
46
59
|
try {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const pageSource = await driver.getPageSource();
|
|
50
|
-
return pageSource;
|
|
60
|
+
const result = await (0, getStableViewHierarchy_1.waitForStableState)();
|
|
61
|
+
return result ?? "";
|
|
51
62
|
}
|
|
52
63
|
catch (_error) {
|
|
53
64
|
return "NO ACTIVE APP FOUND, LAUNCH THE APP TO SEE THE VIEW HIERARCHY";
|
|
@@ -57,8 +68,8 @@ class WebdriverIOAppiumFrameworkDriver {
|
|
|
57
68
|
* Captures a screenshot of the current device screen and saves it to a temp directory.
|
|
58
69
|
* Returns the path to the saved screenshot if successful, or undefined otherwise.
|
|
59
70
|
*/
|
|
60
|
-
async captureSnapshotImage() {
|
|
61
|
-
const tempDir = "
|
|
71
|
+
async captureSnapshotImage(_) {
|
|
72
|
+
const tempDir = path.resolve(os_1.default.tmpdir(), "pilot-snapshot");
|
|
62
73
|
if (!fs.existsSync(tempDir)) {
|
|
63
74
|
fs.mkdirSync(tempDir);
|
|
64
75
|
}
|
|
@@ -155,7 +166,7 @@ class WebdriverIOAppiumFrameworkDriver {
|
|
|
155
166
|
await (await $('~loginButton')).waitForEnabled();
|
|
156
167
|
await (await $('~loginButton')).click();`,
|
|
157
168
|
guidelines: [
|
|
158
|
-
"
|
|
169
|
+
"Allways use await (await $('~element')).waitForEnabled() before clicking on the element",
|
|
159
170
|
],
|
|
160
171
|
},
|
|
161
172
|
{
|
|
@@ -207,6 +218,12 @@ await (await $('~draggable')).dragAndDrop(
|
|
|
207
218
|
"Use this matcher to verify that the element is visible to the user.",
|
|
208
219
|
],
|
|
209
220
|
},
|
|
221
|
+
{
|
|
222
|
+
signature: `.waitForEnabled()`,
|
|
223
|
+
description: "Waits until the element is enabled and ready to be clicked",
|
|
224
|
+
example: `await (await $('~usernameInput')).waitForEnabled();`,
|
|
225
|
+
guidelines: ["Allways use this before clicking on an element"],
|
|
226
|
+
},
|
|
210
227
|
{
|
|
211
228
|
signature: `toExist()`,
|
|
212
229
|
description: "Asserts that the element exists in the DOM/hierarchy.",
|
|
@@ -304,29 +321,6 @@ await driver.setGeoLocation({
|
|
|
304
321
|
latitude: 37.7749,
|
|
305
322
|
longitude: -122.4194,
|
|
306
323
|
altitude: 10
|
|
307
|
-
});
|
|
308
|
-
`,
|
|
309
|
-
},
|
|
310
|
-
{
|
|
311
|
-
signature: `driver.getContext() / driver.switchContext(name: string)`,
|
|
312
|
-
description: "Gets or switches the current context (NATIVE_APP or WEBVIEW_xxx).",
|
|
313
|
-
example: `
|
|
314
|
-
const contexts = await driver.getContexts();
|
|
315
|
-
await driver.switchContext(contexts[1]); // Switch to webview
|
|
316
|
-
await driver.switchContext('NATIVE_APP'); // Switch back
|
|
317
|
-
`,
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
signature: `driver.rotate(params: { x: number; y: number; duration: number; radius: number; rotation: number; touchCount: number })`,
|
|
321
|
-
description: "Simulates a rotation gesture on the device (iOS only).",
|
|
322
|
-
example: `
|
|
323
|
-
await driver.rotate({
|
|
324
|
-
x: 100,
|
|
325
|
-
y: 100,
|
|
326
|
-
duration: 2,
|
|
327
|
-
radius: 50,
|
|
328
|
-
rotation: 180,
|
|
329
|
-
touchCount: 2
|
|
330
324
|
});
|
|
331
325
|
`,
|
|
332
326
|
},
|
|
@@ -365,49 +359,6 @@ await driver.performTouchAction({
|
|
|
365
359
|
},
|
|
366
360
|
],
|
|
367
361
|
},
|
|
368
|
-
{
|
|
369
|
-
title: "Web APIs (Hybrid / Mobile Web)",
|
|
370
|
-
items: [
|
|
371
|
-
{
|
|
372
|
-
signature: `driver.switchContext('WEBVIEW')`,
|
|
373
|
-
description: "Switches the context from native to web view (if a webview is present).",
|
|
374
|
-
example: `
|
|
375
|
-
const contexts = await driver.getContexts();
|
|
376
|
-
await driver.switchContext(contexts.find(c => c.includes('WEBVIEW')));
|
|
377
|
-
`,
|
|
378
|
-
guidelines: [
|
|
379
|
-
"Use this when your app has a webview or is a hybrid app.",
|
|
380
|
-
],
|
|
381
|
-
},
|
|
382
|
-
{
|
|
383
|
-
signature: `$('css or xpath').click()`,
|
|
384
|
-
description: "In a webview context, click (tap) a web element using CSS or XPath.",
|
|
385
|
-
example: `await (await $('button#login')).click();`,
|
|
386
|
-
},
|
|
387
|
-
{
|
|
388
|
-
signature: `$('selector').setValue('text')`,
|
|
389
|
-
description: "In a webview context, sets text in a web input field.",
|
|
390
|
-
example: `await (await $('input#username')).setValue('myusername');`,
|
|
391
|
-
},
|
|
392
|
-
{
|
|
393
|
-
signature: `.getText()`,
|
|
394
|
-
description: "Retrieves the visible text of a web element (hybrid/web context).",
|
|
395
|
-
example: `
|
|
396
|
-
const text = await (await $('h1.main-title')).getText();
|
|
397
|
-
expect(text).toBe('Welcome to My App');
|
|
398
|
-
`,
|
|
399
|
-
},
|
|
400
|
-
{
|
|
401
|
-
signature: `driver.executeScript(script: string, args?: any[])`,
|
|
402
|
-
description: "Executes JavaScript in the context of the webview.",
|
|
403
|
-
example: `
|
|
404
|
-
await driver.executeScript("document.getElementById('hidden-button').click()");
|
|
405
|
-
const title = await driver.executeScript('return document.title');
|
|
406
|
-
expect(title).toBe('My Page Title');
|
|
407
|
-
`,
|
|
408
|
-
},
|
|
409
|
-
],
|
|
410
|
-
},
|
|
411
362
|
],
|
|
412
363
|
};
|
|
413
364
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.waitForStableState = waitForStableState;
|
|
7
|
+
exports.compareViewHierarchies = compareViewHierarchies;
|
|
8
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
|
+
const DEFAULT_POLL_INTERVAL = 500; // ms
|
|
10
|
+
const DEFAULT_TIMEOUT = 5000; // ms
|
|
11
|
+
/** Waits till the view hierarchy is stable than returns it*/
|
|
12
|
+
async function waitForStableState(pollInterval = DEFAULT_POLL_INTERVAL, timeout = DEFAULT_TIMEOUT) {
|
|
13
|
+
const startTime = Date.now();
|
|
14
|
+
let lastSnapshot;
|
|
15
|
+
while (Date.now() - startTime < timeout) {
|
|
16
|
+
const currentSnapshot = (await driver.getPageSource()) || "";
|
|
17
|
+
if (!currentSnapshot) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
if (lastSnapshot) {
|
|
21
|
+
const isStable = compareViewHierarchies(currentSnapshot, lastSnapshot);
|
|
22
|
+
if (isStable) {
|
|
23
|
+
return currentSnapshot;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
lastSnapshot = currentSnapshot;
|
|
27
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
28
|
+
}
|
|
29
|
+
// Return the last snapshot if timeout is reached
|
|
30
|
+
return lastSnapshot;
|
|
31
|
+
}
|
|
32
|
+
function compareViewHierarchies(current, last) {
|
|
33
|
+
// Use MD5 for fast comparison of view hierarchies
|
|
34
|
+
const currentHash = crypto_1.default.createHash("md5").update(current).digest("hex");
|
|
35
|
+
const lastHash = crypto_1.default.createHash("md5").update(last).digest("hex");
|
|
36
|
+
return currentHash === lastHash;
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix-pilot/webdriverio-appium",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "WebdriverIO and Appium driver for Wix Pilot usage",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -19,12 +19,9 @@
|
|
|
19
19
|
"test": "echo No tests available for this package",
|
|
20
20
|
"test:example": "wdio run examples/wdio.conf.ts",
|
|
21
21
|
"build:app": "cd ../detox/ExampleApp && npm run build:ios",
|
|
22
|
-
"
|
|
23
|
-
"release:
|
|
24
|
-
"
|
|
25
|
-
"release:minor": "npm run test && npm run bump-version:minor && npm run build && npm publish --access public",
|
|
26
|
-
"bump-version:major": "npm version major && git commit -am 'chore: bump major version' && git push",
|
|
27
|
-
"release:major": "npm run test && npm run bump-version:major && npm run build && npm publish --access public",
|
|
22
|
+
"release:patch": "node ../../../scripts/release-package.js patch",
|
|
23
|
+
"release:minor": "node ../../../scripts/release-package.js minor",
|
|
24
|
+
"release:major": "node ../../../scripts/release-package.js major",
|
|
28
25
|
"lint": "eslint . --ext .ts",
|
|
29
26
|
"lint:fix": "eslint . --ext .ts --fix",
|
|
30
27
|
"type-check": "tsc --noEmit"
|
|
@@ -32,11 +29,9 @@
|
|
|
32
29
|
"bugs": {
|
|
33
30
|
"url": "https://github.com/wix-incubator/pilot/issues"
|
|
34
31
|
},
|
|
35
|
-
"dependencies": {
|
|
36
|
-
"@wix-pilot/core": "^2.0.0"
|
|
37
|
-
},
|
|
38
32
|
"peerDependencies": {
|
|
39
|
-
"@wdio/globals": ">=8.0.0"
|
|
33
|
+
"@wdio/globals": ">=8.0.0",
|
|
34
|
+
"@wix-pilot/core": "^3.1.6"
|
|
40
35
|
},
|
|
41
36
|
"devDependencies": {
|
|
42
37
|
"@wdio/globals": "^9.9.1",
|
package/dist/wdio.conf.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
export declare const config: {
|
|
2
|
-
runner: string;
|
|
3
|
-
specs: string[];
|
|
4
|
-
maxInstances: number;
|
|
5
|
-
capabilities: {
|
|
6
|
-
platformName: string;
|
|
7
|
-
deviceName: string;
|
|
8
|
-
automationName: string;
|
|
9
|
-
app: string;
|
|
10
|
-
}[];
|
|
11
|
-
logLevel: string;
|
|
12
|
-
bail: number;
|
|
13
|
-
baseUrl: string;
|
|
14
|
-
waitforTimeout: number;
|
|
15
|
-
connectionRetryTimeout: number;
|
|
16
|
-
connectionRetryCount: number;
|
|
17
|
-
services: string[];
|
|
18
|
-
appium: {
|
|
19
|
-
command: string;
|
|
20
|
-
};
|
|
21
|
-
framework: string;
|
|
22
|
-
reporters: string[];
|
|
23
|
-
mochaOpts: {
|
|
24
|
-
ui: string;
|
|
25
|
-
timeout: number;
|
|
26
|
-
};
|
|
27
|
-
before: () => void;
|
|
28
|
-
};
|
package/dist/wdio.conf.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.config = void 0;
|
|
4
|
-
// wdio.conf.ts
|
|
5
|
-
exports.config = {
|
|
6
|
-
runner: 'local',
|
|
7
|
-
specs: ['./examples/**/*.test.ts'],
|
|
8
|
-
maxInstances: 1,
|
|
9
|
-
capabilities: [{
|
|
10
|
-
platformName: 'iOS',
|
|
11
|
-
deviceName: 'iPhone 14',
|
|
12
|
-
automationName: 'XCUITest',
|
|
13
|
-
app: './Users/tzviels/Library/Developer/Xcode/DerivedData/ExampleApp-ccggovixskkojadmtpriqiridvii/Build/Products/Debug-iphonesimulator/ExampleApp.app',
|
|
14
|
-
}],
|
|
15
|
-
logLevel: 'info',
|
|
16
|
-
bail: 0,
|
|
17
|
-
baseUrl: 'http://localhost',
|
|
18
|
-
waitforTimeout: 10000,
|
|
19
|
-
connectionRetryTimeout: 90000,
|
|
20
|
-
connectionRetryCount: 3,
|
|
21
|
-
services: ['appium'],
|
|
22
|
-
appium: {
|
|
23
|
-
command: 'appium',
|
|
24
|
-
},
|
|
25
|
-
framework: 'mocha',
|
|
26
|
-
reporters: ['spec'],
|
|
27
|
-
mochaOpts: {
|
|
28
|
-
ui: 'bdd',
|
|
29
|
-
timeout: 60000,
|
|
30
|
-
},
|
|
31
|
-
before: function () {
|
|
32
|
-
global.driver = browser;
|
|
33
|
-
},
|
|
34
|
-
};
|