From 4aa84501bc73819293b222f0d985dd661e4e2b6d Mon Sep 17 00:00:00 2001 From: KobeNguyenT <7845001+kobenguyent@users.noreply.github.com> Date: Sun, 28 May 2023 22:43:28 +0200 Subject: [PATCH] feat: appium v2 support (#3622) * feat: appium v2 support * Fix: Bump node version * fix: ubuntu bump * fix: try out another selenium version * try another php version * log webdriverio version * bump other wdio libs * fix: typo * fix: undefined error fix [1] Error | TypeError: Cannot read properties of undefined (reading 'restart') * fix: missing appiumV2 config * fix(docs): appium docs * fix: _convertedCaps logic --- .github/workflows/webdriver.yml | 6 ++-- docs/helpers/Appium.md | 1 + docs/mobile.md | 51 +++++++++++++++++++++++++++++++-- lib/helper/Appium.js | 49 +++++++++++++++++++++++++++---- package.json | 6 ++-- 5 files changed, 99 insertions(+), 14 deletions(-) diff --git a/.github/workflows/webdriver.yml b/.github/workflows/webdriver.yml index 96e0a23db..8155890e5 100644 --- a/.github/workflows/webdriver.yml +++ b/.github/workflows/webdriver.yml @@ -22,15 +22,15 @@ jobs: node-version: [16.x] steps: - - run: docker run -d --net=host --shm-size=2g selenium/standalone-chrome:3.141.59-oxygen - - uses: actions/checkout@v3 + - run: docker run -d --net=host --shm-size=2g selenium/standalone-chrome:3.141.0 + - uses: actions/checkout@v1 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 - name: npm install run: | npm install --legacy-peer-deps diff --git a/docs/helpers/Appium.md b/docs/helpers/Appium.md index 70c36aabb..a6fd268a4 100644 --- a/docs/helpers/Appium.md +++ b/docs/helpers/Appium.md @@ -25,6 +25,7 @@ Launch the daemon: `appium` This helper should be configured in codecept.conf.ts or codecept.conf.js +- `appiumV2`: set this to true if you want to run with Appiumv2 - `app`: Application path. Local path or remote URL to an .ipa or .apk file, or a .zip containing one of these. Alias to desiredCapabilities.appPackage - `host`: (default: 'localhost') Appium host - `port`: (default: '4723') Appium port diff --git a/docs/mobile.md b/docs/mobile.md index 541d2319c..702078bb9 100644 --- a/docs/mobile.md +++ b/docs/mobile.md @@ -45,22 +45,69 @@ To install Appium use npm: npm i -g appium ``` +To use Appium 2.x: +```sh +npm i -g appium@next +``` +Appium 2x (still beta) reenvisions Appium as a platform where “drivers” and “plugins” can be easily created and shared independently. +Install an Appium driver and its dependencies +To install the Appium driver and its dependencies, we'll be using the uiautomator2 (Android), XCUITest (iOS) drivers. + +``` +appium driver install xcuitest +appium driver install uiautomator2 +``` +To make sure that all the drivers are installed successfully, run the following command: + +``` +appium driver list + +tth~$appium driver list +✔ Listing available drivers +- espresso@2.17.0 [installed (NPM)] +- uiautomator2@2.12.6 [installed (NPM)] +- xcuitest@4.19.1 [installed (NPM)] +- mac2 [not installed] +- safari [not installed] +- gecko [not installed] +- chromium [not installed] +``` + Then you need to prepare application for execution. It should be packed into apk (for Android) or .ipa (for iOS) or zip. -Next, is to launch the emulator or connect physical device. +Next, is to launch the emulator or connect a physical device. Once they are prepared, launch Appium: ```sh appium ``` +To use Appium 2.x: +```sh +tth~$npx appium --base-path=/wd/hub +[Appium] Welcome to Appium v2.0.0-beta.57 (REV 3e675c32ae71dc0b00749d5d29213e2ea5b53c5b) +[Appium] Non-default server args: +[Appium] { +[Appium] basePath: '/wd/hub' +[Appium] } +[Appium] Attempting to load driver espresso... +[debug] [Appium] Requiring driver at /Users/trung-thanh/Desktop/thanh-nguyen/task2/node_modules/appium-espresso-driver +[Appium] Attempting to load driver uiautomator2... +[debug] [Appium] Requiring driver at /Users/trung-thanh/Desktop/thanh-nguyen/task2/node_modules/appium-uiautomator2-driver +[Appium] Appium REST http interface listener started on 0.0.0.0:4723 +[Appium] Available drivers: +[Appium] - espresso@2.17.0 (automationName 'Espresso') +[Appium] - uiautomator2@2.12.6 (automationName 'UiAutomator2') +[Appium] No plugins have been installed. Use the "appium plugin" command to install the one(s) you want to use. +``` + To run mobile test you need either an device emulator (available with Android SDK or iOS), real device connected for mobile testing. Alternatively, you may execute Appium with device emulator inside Docker container. CodeceptJS should be installed with webdriverio support: ```bash -npm install codeceptjs webdriverio --save +npm install codeceptjs webdriverio@8.6.3 --save ``` ## Configuring diff --git a/lib/helper/Appium.js b/lib/helper/Appium.js index fa11db94f..54248c330 100644 --- a/lib/helper/Appium.js +++ b/lib/helper/Appium.js @@ -17,6 +17,10 @@ const supportedPlatform = { iOS: 'iOS', }; +const vendorPrefix = { + appium: 'appium', +}; + /** * Appium helper extends [Webriver](http://codecept.io/helpers/WebDriver/) helper. * It supports all browser methods and also includes special methods for mobile apps testing. @@ -135,6 +139,9 @@ class Appium extends Webdriver { super(config); this.isRunning = false; + if (config.appiumV2 === true) { + this.appiumV2 = true; + } this.axios = axios.create(); webdriverio = require('webdriverio'); @@ -181,14 +188,22 @@ class Appium extends Webdriver { config.baseUrl = config.url || config.baseUrl; if (config.desiredCapabilities && Object.keys(config.desiredCapabilities).length) { - config.capabilities = config.desiredCapabilities; + config.capabilities = this.appiumV2 === true ? this._convertAppiumV2Caps(config.desiredCapabilities) : config.desiredCapabilities; + } + + if (this.appiumV2) { + config.capabilities[`${vendorPrefix.appium}:deviceName`] = config[`${vendorPrefix.appium}:device`] || config.capabilities[`${vendorPrefix.appium}:deviceName`]; + config.capabilities[`${vendorPrefix.appium}:browserName`] = config[`${vendorPrefix.appium}:browser`] || config.capabilities[`${vendorPrefix.appium}:browserName`]; + config.capabilities[`${vendorPrefix.appium}:app`] = config[`${vendorPrefix.appium}:app`] || config.capabilities[`${vendorPrefix.appium}:app`]; + config.capabilities[`${vendorPrefix.appium}:tunnelIdentifier`] = config[`${vendorPrefix.appium}:tunnelIdentifier`] || config.capabilities[`${vendorPrefix.appium}:tunnelIdentifier`]; // Adding the code to connect to sauce labs via sauce tunnel + } else { + config.capabilities.deviceName = config.device || config.capabilities.deviceName; + config.capabilities.browserName = config.browser || config.capabilities.browserName; + config.capabilities.app = config.app || config.capabilities.app; + config.capabilities.tunnelIdentifier = config.tunnelIdentifier || config.capabilities.tunnelIdentifier; // Adding the code to connect to sauce labs via sauce tunnel } - config.capabilities.deviceName = config.device || config.capabilities.deviceName; - config.capabilities.browserName = config.browser || config.capabilities.browserName; - config.capabilities.app = config.app || config.capabilities.app; config.capabilities.platformName = config.platform || config.capabilities.platformName; - config.capabilities.tunnelIdentifier = config.tunnelIdentifier || config.capabilities.tunnelIdentifier; // Adding the code to connect to sauce labs via sauce tunnel config.waitForTimeoutInSeconds = config.waitForTimeout / 1000; // convert to seconds // [CodeceptJS compatible] transform host to hostname @@ -203,6 +218,10 @@ class Appium extends Webdriver { } this.platform = null; + if (config.capabilities[`${vendorPrefix.appium}:platformName`]) { + this.platform = config.capabilities[`${vendorPrefix.appium}:platformName`].toLowerCase(); + } + if (config.capabilities.platformName) { this.platform = config.capabilities.platformName.toLowerCase(); } @@ -210,6 +229,18 @@ class Appium extends Webdriver { return config; } + _convertAppiumV2Caps(capabilities) { + const _convertedCaps = {}; + for (const [key, value] of Object.entries(capabilities)) { + if (!key.startsWith(vendorPrefix.appium)) { + _convertedCaps[`${vendorPrefix.appium}:${key}`] = value; + } else { + _convertedCaps[`${key}`] = value; + } + } + return _convertedCaps; + } + static _config() { return [{ name: 'app', @@ -229,6 +260,11 @@ class Appium extends Webdriver { } async _startBrowser() { + if (this.appiumV2 === true) { + this.options.capabilities = this._convertAppiumV2Caps(this.options.capabilities); + this.options.desiredCapabilities = this._convertAppiumV2Caps(this.options.desiredCapabilities); + } + try { if (this.options.multiremote) { this.browser = await webdriverio.multiremote(this.options.multiremote); @@ -445,6 +481,7 @@ class Appium extends Webdriver { */ async checkIfAppIsInstalled(bundleId) { onlyForApps.call(this, supportedPlatform.android); + return this.browser.isAppInstalled(bundleId); } @@ -481,7 +518,7 @@ class Appium extends Webdriver { async seeAppIsNotInstalled(bundleId) { onlyForApps.call(this, supportedPlatform.android); const res = await this.browser.isAppInstalled(bundleId); - return truth(`app ${bundleId}`, 'to be installed').negate(res); + return truth(`app ${bundleId}`, 'not to be installed').negate(res); } /** diff --git a/package.json b/package.json index 3c1c7cb44..8c0712a17 100644 --- a/package.json +++ b/package.json @@ -97,9 +97,9 @@ "@pollyjs/core": "^5.1.0", "@types/inquirer": "^0.0.35", "@types/node": "^8.10.66", - "@wdio/sauce-service": "^5.22.5", - "@wdio/selenium-standalone-service": "^5.16.10", - "@wdio/utils": "^5.23.0", + "@wdio/sauce-service": "^8.3.8", + "@wdio/selenium-standalone-service": "^8.3.2", + "@wdio/utils": "^8.3.0", "apollo-server-express": "^2.25.3", "chai-as-promised": "^7.1.1", "chai-subset": "^1.6.0",