@wix-pilot/webdriverio-appium 1.0.2 → 1.0.3

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 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 = __importDefault(require("@wix-pilot/core"));
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.default.init({
12
+ pilot = new core_1.Pilot({
15
13
  frameworkDriver,
16
14
  promptHandler,
17
15
  });
18
16
  });
19
17
  beforeEach(async () => {
20
- core_1.default.start();
18
+ pilot.start();
21
19
  });
22
20
  afterEach(async () => {
23
- core_1.default.end();
21
+ pilot.end();
24
22
  });
25
23
  it("perform test with pilot", async () => {
26
- await core_1.default.autopilot("earn 2 points in the game");
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.
package/dist/index.js CHANGED
@@ -36,18 +36,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.WebdriverIOAppiumFrameworkDriver = void 0;
37
37
  const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
+ const getStableViewHierarchy_1 = require("./utils/getStableViewHierarchy");
39
40
  class WebdriverIOAppiumFrameworkDriver {
40
41
  constructor() { }
42
+ /**
43
+ * Additional driver configuration.
44
+ *
45
+ * @property useSnapshotStabilitySync - Indicates whether the driver should use wait for screen stability.
46
+ */
47
+ get driverConfig() {
48
+ return { useSnapshotStabilitySync: true };
49
+ }
41
50
  /**
42
51
  * Attempts to capture the current view hierarchy (source) of the mobile app as XML.
43
52
  * If there's no active session or the app isn't running, returns an error message.
44
53
  */
45
54
  async captureViewHierarchyString() {
46
55
  try {
47
- // In WebdriverIO + Appium, you can retrieve the current page source (UI hierarchy) via:
48
- // https://webdriver.io/docs/api/browser/getPageSource (driver is an alias for browser)
49
- const pageSource = await driver.getPageSource();
50
- return pageSource;
56
+ const result = await (0, getStableViewHierarchy_1.waitForStableState)();
57
+ return result ?? "";
51
58
  }
52
59
  catch (_error) {
53
60
  return "NO ACTIVE APP FOUND, LAUNCH THE APP TO SEE THE VIEW HIERARCHY";
@@ -155,7 +162,7 @@ class WebdriverIOAppiumFrameworkDriver {
155
162
  await (await $('~loginButton')).waitForEnabled();
156
163
  await (await $('~loginButton')).click();`,
157
164
  guidelines: [
158
- "Before clicking on an element make sure it is enabled",
165
+ "Allways use await (await $('~element')).waitForEnabled() before clicking on the element",
159
166
  ],
160
167
  },
161
168
  {
@@ -207,6 +214,12 @@ await (await $('~draggable')).dragAndDrop(
207
214
  "Use this matcher to verify that the element is visible to the user.",
208
215
  ],
209
216
  },
217
+ {
218
+ signature: `.waitForEnabled()`,
219
+ description: "Waits until the element is enabled and ready to be clicked",
220
+ example: `await (await $('~usernameInput')).waitForEnabled();`,
221
+ guidelines: ["Allways use this before clicking on an element"],
222
+ },
210
223
  {
211
224
  signature: `toExist()`,
212
225
  description: "Asserts that the element exists in the DOM/hierarchy.",
@@ -304,29 +317,6 @@ await driver.setGeoLocation({
304
317
  latitude: 37.7749,
305
318
  longitude: -122.4194,
306
319
  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
320
  });
331
321
  `,
332
322
  },
@@ -365,49 +355,6 @@ await driver.performTouchAction({
365
355
  },
366
356
  ],
367
357
  },
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
358
  ],
412
359
  };
413
360
  }
@@ -0,0 +1,3 @@
1
+ /** Waits till the view hierarchy is stable than returns it*/
2
+ export declare function waitForStableState(pollInterval?: number, timeout?: number): Promise<string | undefined>;
3
+ export declare function compareViewHierarchies(current: string, last: string): boolean;
@@ -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.2",
3
+ "version": "1.0.3",
4
4
  "description": "WebdriverIO and Appium driver for Wix Pilot usage",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",