From fb84f8952278c69f04584f04d2ec630598b30015 Mon Sep 17 00:00:00 2001 From: kobenguyent Date: Wed, 1 Nov 2023 14:11:27 +0100 Subject: [PATCH] fix: deprecate appium 1.x --- .github/workflows/appium.yml | 3 +- .github/workflows/appiumV2.yml | 59 +++ lib/helper/Appium.js | 1 + test/helper/AppiumV2Web_test.js | 163 +++++++ test/helper/AppiumV2_test.js | 745 ++++++++++++++++++++++++++++++++ 5 files changed, 969 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/appiumV2.yml create mode 100644 test/helper/AppiumV2Web_test.js create mode 100644 test/helper/AppiumV2_test.js diff --git a/.github/workflows/appium.yml b/.github/workflows/appium.yml index 25d414e26..25c3bfca3 100644 --- a/.github/workflows/appium.yml +++ b/.github/workflows/appium.yml @@ -4,8 +4,7 @@ on: push: branches: - 3.x - - feat/appium-is-app-installed - + - env: CI: true # Force terminal colors. @see https://www.npmjs.com/package/colors diff --git a/.github/workflows/appiumV2.yml b/.github/workflows/appiumV2.yml new file mode 100644 index 000000000..50ea3a935 --- /dev/null +++ b/.github/workflows/appiumV2.yml @@ -0,0 +1,59 @@ +name: Appium V2 Tests + +on: + push: + branches: + - 3.x + - appium-v1-deprecation + +env: + CI: true + # Force terminal colors. @see https://www.npmjs.com/package/colors + FORCE_COLOR: 1 + +jobs: + appium1: + runs-on: ubuntu-20.04 + + strategy: + matrix: + node-version: [16.x] + + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - run: npm install --legacy-peer-deps + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: true + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true + - run: 'npm run test:appium-quick' + env: # Or as an environment variable + SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} + SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} + + + appium2: + + runs-on: ubuntu-20.04 + + strategy: + matrix: + node-version: [16.x] + + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - run: npm install --legacy-peer-deps + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: true + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true + - run: 'npm run test:appium-other' + env: # Or as an environment variable + SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} + SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} diff --git a/lib/helper/Appium.js b/lib/helper/Appium.js index c6cc7c208..6c77fdf35 100644 --- a/lib/helper/Appium.js +++ b/lib/helper/Appium.js @@ -183,6 +183,7 @@ class Appium extends Webdriver { this.axios = axios.create(); webdriverio = require('webdriverio'); + console.log('The Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. Please migrating to Appium 2.x by adding appiumV2: true to your config.\nThis Appium 1.x support will be removed in next major release'); } _validateConfig(config) { diff --git a/test/helper/AppiumV2Web_test.js b/test/helper/AppiumV2Web_test.js new file mode 100644 index 000000000..ae61a022f --- /dev/null +++ b/test/helper/AppiumV2Web_test.js @@ -0,0 +1,163 @@ +const Appium = require('../../lib/helper/Appium'); +global.codeceptjs = require('../../lib'); + +let I; +const site_url = 'http://davertmik.github.io'; + +describe('Appium Web', function () { + this.retries(4); + this.timeout(70000); + + before(() => { + I = new Appium({ + url: site_url, + appiumV2: true, + browser: 'chrome', + restart: false, + desiredCapabilities: { + appiumVersion: '2.0.0', + recordVideo: 'false', + recordScreenshots: 'false', + platformName: 'Android', + platformVersion: '6.0', + deviceName: 'Android Emulator', + }, + host: 'ondemand.saucelabs.com', + port: 80, + // port: 4723, + // host: 'localhost', + user: process.env.SAUCE_USERNAME, + key: process.env.SAUCE_ACCESS_KEY, + }); + // I.isWeb = true; + I._init(); + I._beforeSuite(); + }); + + after(() => I._finishTest()); + + beforeEach(() => { + I.isWeb = true; + return I._before(); + }); + + afterEach(() => I._after()); + + describe('current url : #seeInCurrentUrl, #seeCurrentUrlEquals, ...', () => { + it('should check for url fragment', async () => { + await I.amOnPage('/angular-demo-app/#/info'); + await I.seeInCurrentUrl('/info'); + await I.dontSeeInCurrentUrl('/result'); + }); + + it('should check for equality', async () => { + await I.amOnPage('/angular-demo-app/#/info'); + await I.seeCurrentUrlEquals('/angular-demo-app/#/info'); + await I.dontSeeCurrentUrlEquals('/angular-demo-app/#/result'); + }); + }); + + describe('see text : #see', () => { + it('should check text on site', async () => { + await I.amOnPage('/angular-demo-app/'); + await I.see('Description'); + await I.dontSee('Create Event Today'); + }); + + it('should check text inside element', async () => { + await I.amOnPage('/angular-demo-app/#/info'); + await I.see('About', 'h1'); + await I.see('Welcome to event app', { css: 'p.jumbotron' }); + await I.see('Back to form', '//div/a'); + }); + }); + + describe('see element : #seeElement, #dontSeeElement', () => { + it('should check visible elements on page', async () => { + await I.amOnPage('/angular-demo-app/'); + await I.seeElement('.btn.btn-primary'); + await I.seeElement({ css: '.btn.btn-primary' }); + await I.dontSeeElement({ css: '.btn.btn-secondary' }); + }); + }); + + describe('#click', () => { + it('should click by text', async () => { + await I.amOnPage('/angular-demo-app/'); + await I.dontSeeInCurrentUrl('/info'); + await I.click('Get more info!'); + await I.seeInCurrentUrl('/info'); + }); + + it('should click by css', async () => { + await I.amOnPage('/angular-demo-app/'); + await I.click('.btn-primary'); + await I.wait(2); + await I.seeInCurrentUrl('/result'); + }); + + it('should click by non-optimal css', async () => { + await I.amOnPage('/angular-demo-app/'); + await I.click('form a.btn'); + await I.wait(2); + await I.seeInCurrentUrl('/result'); + }); + + it('should click by xpath', async () => { + await I.amOnPage('/angular-demo-app/'); + await I.click('//a[contains(., "more info")]'); + await I.seeInCurrentUrl('/info'); + }); + + it('should click on context', async () => { + await I.amOnPage('/angular-demo-app/'); + await I.click('.btn-primary', 'form'); + await I.wait(2); + await I.seeInCurrentUrl('/result'); + }); + + it('should click link with inner span', async () => { + await I.amOnPage('/angular-demo-app/#/result'); + await I.click('Go to info'); + await I.seeInCurrentUrl('/info'); + }); + + it('should click buttons as links', async () => { + await I.amOnPage('/angular-demo-app/'); + await I.click('Options'); + await I.seeInCurrentUrl('/options'); + }); + }); + + describe('#grabTextFrom, #grabValueFrom, #grabAttributeFrom', () => { + it('should grab text from page', async () => { + await I.amOnPage('/angular-demo-app/#/info'); + const val = await I.grabTextFrom('p.jumbotron'); + val.should.be.equal('Welcome to event app'); + }); + + it('should grab value from field', async () => { + await I.amOnPage('/angular-demo-app/#/options'); + const val = await I.grabValueFrom('#ssh'); + val.should.be.equal('PUBLIC-SSH-KEY'); + }); + + it('should grab attribute from element', async () => { + await I.amOnPage('/angular-demo-app/#/info'); + const val = await I.grabAttributeFrom('a.btn', 'ng-href'); + val.should.be.equal('#/'); + }); + }); + + describe('#within', () => { + afterEach(() => I._withinEnd()); + + it('should work using within operator', async () => { + await I.amOnPage('/angular-demo-app/#/options'); + await I.see('Choose if you ok with terms'); + await I._withinBegin({ css: 'div.results' }); + await I.see('SSH Public Key: PUBLIC-SSH-KEY'); + await I.dontSee('Options'); + }); + }); +}); diff --git a/test/helper/AppiumV2_test.js b/test/helper/AppiumV2_test.js new file mode 100644 index 000000000..29a76cb36 --- /dev/null +++ b/test/helper/AppiumV2_test.js @@ -0,0 +1,745 @@ +const assert = require('assert'); +const { expect } = require('chai'); +const path = require('path'); + +const Appium = require('../../lib/helper/Appium'); +const AssertionFailedError = require('../../lib/assert/error'); +const fileExists = require('../../lib/utils').fileExists; +global.codeceptjs = require('../../lib'); + +let app; +const apk_path = 'storage:filename=selendroid-test-app-0.17.0.apk'; +const smallWait = 3; + +describe('Appium', function () { + // this.retries(1); + this.timeout(0); + + before(async () => { + global.codecept_dir = path.join(__dirname, '/../data'); + app = new Appium({ + app: apk_path, + appiumV2: true, + desiredCapabilities: { + appiumVersion: '2.0.0', + browserName: '', + recordVideo: 'false', + recordScreenshots: 'false', + platformName: 'Android', + platformVersion: '6.0', + deviceName: 'Android Emulator', + androidInstallTimeout: 90000, + appWaitDuration: 300000, + }, + restart: true, + protocol: 'http', + host: 'ondemand.saucelabs.com', + port: 80, + // port: 4723, + // host: 'localhost', + user: process.env.SAUCE_USERNAME, + key: process.env.SAUCE_ACCESS_KEY, + }); + await app._beforeSuite(); + app.isWeb = false; + await app._before(); + }); + + after(async () => { + await app._after(); + }); + + describe('app installation : #seeAppIsInstalled, #installApp, #removeApp, #seeAppIsNotInstalled', () => { + describe( + '#grabAllContexts, #grabContext, #grabCurrentActivity, #grabNetworkConnection, #grabOrientation, #grabSettings', + () => { + it('should grab all available contexts for screen', async () => { + await app.resetApp(); + await app.waitForElement('~buttonStartWebviewCD', smallWait); + await app.click('~buttonStartWebviewCD'); + const val = await app.grabAllContexts(); + assert.deepEqual(val, ['NATIVE_APP', 'WEBVIEW_io.selendroid.testapp']); + }); + + it('should grab current context', async () => { + const val = await app.grabContext(); + assert.equal(val, 'NATIVE_APP'); + }); + + it('should grab current activity of app', async () => { + const val = await app.grabCurrentActivity(); + assert.equal(val, '.HomeScreenActivity'); + }); + + it('should grab network connection settings', async () => { + await app.setNetworkConnection(4); + const val = await app.grabNetworkConnection(); + assert.equal(val.value, 4); + assert.equal(val.inAirplaneMode, false); + assert.equal(val.hasWifi, false); + assert.equal(val.hasData, true); + }); + + it('should grab orientation', async () => { + const val = await app.grabOrientation(); + assert.equal(val, 'PORTRAIT'); + }); + + it('should grab custom settings', async () => { + const val = await app.grabSettings(); + assert.deepEqual(val, { ignoreUnimportantViews: false }); + }); + }, + ); + + it('should remove App and install it again', async () => { + await app.seeAppIsInstalled('io.selendroid.testapp'); + await app.removeApp('io.selendroid.testapp'); + await app.seeAppIsNotInstalled('io.selendroid.testapp'); + await app.installApp(apk_path); + await app.seeAppIsInstalled('io.selendroid.testapp'); + }); + + it('should return true if app is installed @quick', async () => { + const status = await app.checkIfAppIsInstalled('io.selendroid.testapp'); + expect(status).to.be.true; + }); + + it('should assert when app is/is not installed', async () => { + try { + await app.seeAppIsInstalled('io.super.app'); + } catch (e) { + e.should.be.instanceOf(AssertionFailedError); + e.inspect().should.include('expected app io.super.app to be installed'); + } + + try { + await app.seeAppIsNotInstalled('io.selendroid.testapp'); + } catch (e) { + e.should.be.instanceOf(AssertionFailedError); + e.inspect().should.include('expected app io.selendroid.testapp not to be installed'); + } + }); + }); + + describe('see seeCurrentActivity: #seeCurrentActivityIs', () => { + it('should return .HomeScreenActivity for default screen', async () => { + await app.seeCurrentActivityIs('.HomeScreenActivity'); + }); + + it('should assert for wrong screen', async () => { + try { + await app.seeCurrentActivityIs('.SuperScreen'); + } catch (e) { + e.should.be.instanceOf(AssertionFailedError); + e.inspect().should.include('expected current activity to be .SuperScreen'); + } + }); + }); + + describe('device lock : #seeDeviceIsLocked, #seeDeviceIsUnlocked', () => { + it('should return correct status about lock @second', async () => { + await app.seeDeviceIsUnlocked(); + try { + await app.seeDeviceIsLocked(); + } catch (e) { + e.should.be.instanceOf(AssertionFailedError); + e.inspect().should.include('expected device to be locked'); + } + }); + }); + + describe('device orientation : #seeOrientationIs #setOrientation', () => { + it('should return correct status about lock', async () => { + await app.seeOrientationIs('PORTRAIT'); + try { + await app.seeOrientationIs('LANDSCAPE'); + } catch (e) { + e.should.be.instanceOf(AssertionFailedError); + e.inspect().should.include('expected orientation to be LANDSCAPE'); + } + }); + + it('should set device orientation', async () => { + await app.resetApp(); + await app.waitForElement('~buttonStartWebviewCD', smallWait); + await app.click('~buttonStartWebviewCD'); + await app.setOrientation('LANDSCAPE'); + await app.seeOrientationIs('LANDSCAPE'); + }); + }); + + describe('app context and activity: #_switchToContext, #switchToWeb, #switchToNative', () => { + it('should switch context', async () => { + await app.resetApp(); + await app.waitForElement('~buttonStartWebviewCD', smallWait); + await app.click('~buttonStartWebviewCD'); + await app._switchToContext('WEBVIEW_io.selendroid.testapp'); + const val = await app.grabContext(); + return assert.equal(val, 'WEBVIEW_io.selendroid.testapp'); + }); + + it('should switch to native and web contexts @quick', async () => { + await app.resetApp(); + await app.click('~buttonStartWebviewCD'); + await app.see('WebView location'); + await app.switchToWeb(); + let val = await app.grabContext(); + assert.equal(val, 'WEBVIEW_io.selendroid.testapp'); + await app.see('Prefered Car'); + assert.ok(app.isWeb); + await app.switchToNative(); + val = await app.grabContext(); + assert.equal(val, 'NATIVE_APP'); + return assert.ok(!app.isWeb); + }); + + it('should switch activity', async () => { + await app.startActivity('io.selendroid.testapp', '.RegisterUserActivity'); + const val = await app.grabCurrentActivity(); + assert.equal(val, '.RegisterUserActivity'); + }); + }); + + describe('#setNetworkConnection, #setSettings', () => { + it('should set Network Connection (airplane mode on)', async () => { + await app.setNetworkConnection(1); + const val = await app.grabNetworkConnection(); + return assert.equal(val.value, 1); + }); + + it('should set custom settings', async () => { + await app.setSettings({ cyberdelia: 'open' }); + const val = await app.grabSettings(); + assert.deepEqual(val, { ignoreUnimportantViews: false, cyberdelia: 'open' }); + }); + }); + + describe('#hideDeviceKeyboard', () => { + it('should hide device Keyboard @quick', async () => { + await app.resetApp(); + await app.click('~startUserRegistrationCD'); + try { + await app.click('//android.widget.CheckBox'); + } catch (e) { + e.message.should.include('element'); + } + await app.hideDeviceKeyboard('pressKey', 'Done'); + await app.click('//android.widget.CheckBox'); + }); + + it('should assert if no keyboard', async () => { + try { + await app.hideDeviceKeyboard('pressKey', 'Done'); + } catch (e) { + e.message.should.include('An unknown server-side error occurred while processing the command. Original error: Soft keyboard not present, cannot hide keyboard'); + } + }); + }); + + describe('#sendDeviceKeyEvent', () => { + it('should react on pressing keycode', async () => { + await app.sendDeviceKeyEvent(3); + await app.waitForVisible('~Apps'); + }); + }); + + describe('#openNotifications', () => { + it('should react on notification opening', async () => { + try { + await app.seeElement('//android.widget.FrameLayout[@resource-id="com.android.systemui:id/quick_settings_container"]'); + } catch (e) { + e.should.be.instanceOf(AssertionFailedError); + e.inspect().should.include('expected elements of //android.widget.FrameLayout[@resource-id="com.android.systemui:id/quick_settings_container"] to be seen'); + } + await app.openNotifications(); + await app.waitForVisible('//android.widget.FrameLayout[@resource-id="com.android.systemui:id/quick_settings_container"]', 10); + }); + }); + + describe('#makeTouchAction', () => { + it('should react on touch actions', async () => { + await app.resetApp(); + await app.waitForElement('~buttonStartWebviewCD', smallWait); + await app.tap('~buttonStartWebviewCD'); + const val = await app.grabCurrentActivity(); + assert.equal(val, '.WebViewActivity'); + }); + + it('should react on swipe action', async () => { + await app.click("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']"); + await app.waitForText( + 'Gesture Type', + 10, + "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']", + ); + await app.swipe( + "//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", + 800, + 1200, + 1000, + ); + const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']"); + const vx = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view3']"); + const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']"); + assert.equal(type, 'FLICK'); + assert.ok(vx.match(/vx: \d\d000\.0 pps/), 'to be like \d\d000.0 pps'); + assert.ok(vy.match(/vy: \d\d000\.0 pps/), 'to be like \d\d000.0 pps'); + }); + + it('should react on swipeDown action', async () => { + await app.click("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']"); + await app.waitForText( + 'Gesture Type', + 10, + "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']", + ); + await app.swipeDown( + "//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", + 1200, + 1000, + ); + const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']"); + const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']"); + assert.equal(type, 'FLICK'); + assert.ok(vy.match(/vy: \d\d000\.0 pps/), 'to be like \d\d000.0 pps'); + }); + + it('run simplified swipeDown @quick', async () => { + await app.resetApp(); + await app.click("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']"); + await app.waitForText( + 'Gesture Type', + 10, + "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']", + ); + await app.swipeDown( + "//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", + 120, + 100, + ); + const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']"); + assert.equal(type, 'FLICK'); + }); + + it('should react on swipeUp action', async () => { + await app.click("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']"); + await app.waitForText( + 'Gesture Type', + 10, + "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']", + ); + await app.swipeUp( + "//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", + 1200, + 1000, + ); + const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']"); + const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']"); + assert.equal(type, 'FLICK'); + assert.ok(vy.match(/vy: -\d\d000\.0 pps/), 'to be like \d\d000.0 pps'); + }); + + it('should react on swipeRight action', async () => { + await app.click("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']"); + await app.waitForText( + 'Gesture Type', + 10, + "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']", + ); + await app.swipeRight( + "//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", + 800, + 1000, + ); + const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']"); + const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view3']"); + assert.equal(type, 'FLICK'); + assert.ok(vy.match(/vx: \d\d000\.0 pps/), 'to be like \d\d000.0 pps'); + }); + + it('should react on swipeLeft action', async () => { + await app.click("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']"); + await app.waitForText( + 'Gesture Type', + 10, + "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']", + ); + await app.swipeLeft( + "//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", + 800, + 1000, + ); + const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']"); + const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view3']"); + assert.equal(type, 'FLICK'); + assert.ok(vy.match(/vx: -\d\d000\.0 pps/), 'to be like 21000.0 pps'); + }); + + it('should react on touchPerform action', async () => { + await app.touchPerform([{ + action: 'press', + options: { + x: 100, + y: 200, + }, + }, { action: 'release' }]); + const val = await app.grabCurrentActivity(); + assert.equal(val, '.HomeScreenActivity'); + }); + + it('should assert when you dont scroll the document anymore', async () => { + await app.resetApp(); + await app.waitForElement('~startUserRegistrationCD', smallWait); + await app.click('~startUserRegistrationCD'); + try { + await app.swipeTo( + '//android.widget.CheckBox', + '//android.widget.ScrollView/android.widget.LinearLayout', + 'up', + 30, + 100, + 500, + ); + } catch (e) { + e.message.should.include('Scroll to the end and element android.widget.CheckBox was not found'); + } + }); + + it('should react on swipeTo action', async () => { + await app.resetApp(); + await app.waitForElement('~startUserRegistrationCD', smallWait); + await app.click('~startUserRegistrationCD'); + await app.swipeTo( + '//android.widget.CheckBox', + '//android.widget.ScrollView/android.widget.LinearLayout', + 'up', + 30, + 100, + 700, + ); + }); + + describe('#performTouchAction', () => { + it('should react on swipeUp action @second', async () => { + await app.click("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']"); + await app.waitForText( + 'Gesture Type', + 10, + "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']", + ); + await app.swipeUp("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']"); + const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']"); + const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']"); + assert.equal(type, 'FLICK'); + expect(parseInt(vy.split(' ')[1], 10)).to.be.below(1006); + }); + + it('should react on swipeDown action @second', async () => { + await app.resetApp(); + await app.click("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']"); + await app.waitForText( + 'Gesture Type', + 10, + "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']", + ); + await app.swipeUp("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']"); + const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']"); + const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']"); + assert.equal(type, 'FLICK'); + expect(parseInt(vy.split(' ')[1], 10)).to.be.above(-300); + }); + + it('should react on swipeLeft action', async () => { + await app.click("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']"); + await app.waitForText( + 'Gesture Type', + 10, + "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']", + ); + await app.swipeLeft("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']"); + const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']"); + const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']"); + assert.equal(type, 'FLICK'); + expect(vy.split(' ')[1]).to.be.below(730); + }); + + it('should react on swipeRight action', async () => { + await app.click("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']"); + await app.waitForText( + 'Gesture Type', + 10, + "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']", + ); + await app.swipeRight("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']"); + const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']"); + const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']"); + assert.equal(type, 'FLICK'); + expect(vy.split(' ')[1]).to.be.above(278); + }); + }); + }); + + describe('#pullFile', () => { + it('should pull file to local machine', async () => { + const savepath = path.join(__dirname, `/../data/output/testpullfile${new Date().getTime()}.png`); + await app.pullFile('/storage/emulated/0/DCIM/sauce_logo.png', savepath); + assert.ok(fileExists(savepath), null, 'file does not exists'); + }); + }); + + describe('see text : #see', () => { + it('should work inside elements @second', async () => { + await app.resetApp(); + await app.see('EN Button', '~buttonTestCD'); + await app.see('Hello'); + await app.dontSee('Welcome', '~buttonTestCD'); + }); + + it('should work inside web view as normally @quick', async () => { + await app.resetApp(); + await app.click('~buttonStartWebviewCD'); + await app.switchToWeb(); + await app.see('Prefered Car:'); + }); + }); + + describe('#appendField', () => { + it('should be able to send special keys to element @second', async () => { + await app.resetApp(); + await app.waitForElement('~startUserRegistrationCD', smallWait); + await app.click('~startUserRegistrationCD'); + await app.click('~email of the customer'); + await app.appendField('~email of the customer', '1'); + await app.hideDeviceKeyboard('pressKey', 'Done'); + await app.swipeTo( + '//android.widget.Button', + '//android.widget.ScrollView/android.widget.LinearLayout', + 'up', + 30, + 100, + 700, + ); + await app.click('//android.widget.Button'); + await app.see( + '1', + '#io.selendroid.testapp:id/label_email_data', + ); + }); + }); + + describe('#seeInSource', () => { + it('should check for text to be in HTML source', async () => { + await app.seeInSource('class="android.widget.Button" package="io.selendroid.testapp" content-desc="buttonTestCD"'); + await app.dontSeeInSource(' { + it('should return error if not present', async () => { + try { + await app.waitForText('Nothing here', 1, '~buttonTestCD'); + } catch (e) { + e.should.be.instanceOf(AssertionFailedError); + e.inspect().should.be.equal('expected element ~buttonTestCD to include "Nothing here"'); + } + }); + }); + + describe('#seeNumberOfElements @second', () => { + it('should return 1 as count', async () => { + await app.resetApp(); + await app.seeNumberOfElements('~buttonTestCD', 1); + }); + }); + + describe('see element : #seeElement, #dontSeeElement', () => { + it('should check visible elements on page @quick', async () => { + await app.resetApp(); + await app.seeElement('//android.widget.Button[@content-desc = "buttonTestCD"]'); + await app.dontSeeElement('#something-beyond'); + await app.dontSeeElement('//input[@id="something-beyond"]'); + }); + }); + + describe('#click @quick', () => { + it('should click by accessibility id', async () => { + await app.resetApp(); + await app.tap('~startUserRegistrationCD'); + await app.seeElement('//android.widget.TextView[@content-desc="label_usernameCD"]'); + }); + + it('should click by xpath', async () => { + await app.resetApp(); + await app.click('//android.widget.ImageButton[@content-desc = "startUserRegistrationCD"]'); + await app.seeElement('//android.widget.TextView[@content-desc="label_usernameCD"]'); + }); + }); + + describe('#fillField, #appendField @second', () => { + it('should fill field by accessibility id', async () => { + await app.resetApp(); + await app.waitForElement('~startUserRegistrationCD', smallWait); + await app.click('~startUserRegistrationCD'); + await app.fillField('~email of the customer', 'Nothing special'); + await app.hideDeviceKeyboard('pressKey', 'Done'); + await app.swipeTo( + '//android.widget.Button', + '//android.widget.ScrollView/android.widget.LinearLayout', + 'up', + 30, + 100, + 700, + ); + await app.click('//android.widget.Button'); + await app.see( + 'Nothing special', + '//android.widget.TextView[@resource-id="io.selendroid.testapp:id/label_email_data"]', + ); + }); + + it('should fill field by xpath', async () => { + await app.resetApp(); + await app.waitForElement('~startUserRegistrationCD', smallWait); + await app.click('~startUserRegistrationCD'); + await app.fillField('//android.widget.EditText[@content-desc="email of the customer"]', 'Nothing special'); + await app.hideDeviceKeyboard('pressKey', 'Done'); + await app.swipeTo( + '//android.widget.Button', + '//android.widget.ScrollView/android.widget.LinearLayout', + 'up', + 30, + 100, + 700, + ); + await app.click('//android.widget.Button'); + await app.see( + 'Nothing special', + '//android.widget.TextView[@resource-id="io.selendroid.testapp:id/label_email_data"]', + ); + }); + + it('should append field value @second', async () => { + await app.resetApp(); + await app.waitForElement('~startUserRegistrationCD', smallWait); + await app.click('~startUserRegistrationCD'); + await app.fillField('~email of the customer', 'Nothing special'); + await app.appendField('~email of the customer', 'blabla'); + await app.hideDeviceKeyboard('pressKey', 'Done'); + await app.swipeTo( + '//android.widget.Button', + '//android.widget.ScrollView/android.widget.LinearLayout', + 'up', + 30, + 100, + 700, + ); + await app.click('//android.widget.Button'); + await app.see( + 'Nothing specialblabla', + '//android.widget.TextView[@resource-id="io.selendroid.testapp:id/label_email_data"]', + ); + }); + }); + + describe('#clearField', () => { + it('should clear a given element', async () => { + await app.resetApp(); + await app.waitForElement('~startUserRegistrationCD', smallWait); + await app.click('~startUserRegistrationCD'); + await app.fillField('~email of the customer', 'Nothing special'); + await app.see('Nothing special', '~email of the customer'); + await app.clearField('~email of the customer'); + await app.dontSee('Nothing special', '~email of the customer'); + }); + }); + + describe('#grabTextFrom, #grabValueFrom, #grabAttributeFrom @quick', () => { + it('should grab text from page', async () => { + await app.resetApp(); + const val = await app.grabTextFrom('//android.widget.Button[@content-desc="buttonTestCD"]'); + assert.equal(val, 'EN Button'); + }); + + it('should grab attribute from element', async () => { + await app.resetApp(); + const val = await app.grabAttributeFrom('//android.widget.Button[@content-desc="buttonTestCD"]', 'resourceId'); + assert.equal(val, 'io.selendroid.testapp:id/buttonTest'); + }); + + it('should be able to grab elements', async () => { + await app.resetApp(); + await app.tap('~startUserRegistrationCD'); + await app.tap('~email of the customer'); + await app.appendField('//android.widget.EditText[@content-desc="email of the customer"]', '1'); + await app.hideDeviceKeyboard('pressKey', 'Done'); + await app.swipeTo( + '//android.widget.Button', + '//android.widget.ScrollView/android.widget.LinearLayout', + 'up', + 30, + 100, + 700, + ); + await app.click('//android.widget.Button'); + await app.see( + '1', + '//android.widget.TextView[@resource-id="io.selendroid.testapp:id/label_email_data"]', + ); + const id = await app.grabNumberOfVisibleElements( + '//android.widget.TextView[@resource-id="io.selendroid.testapp:id/label_email_data"]', + 'contentDescription', + ); + assert.strictEqual(1, id); + }); + }); + + describe('#saveScreenshot @quick', () => { + beforeEach(() => { + global.output_dir = path.join(global.codecept_dir, 'output'); + }); + + it('should create a screenshot file in output dir', async () => { + const sec = (new Date()).getUTCMilliseconds(); + await app.saveScreenshot(`screenshot_${sec}.png`); + assert.ok(fileExists(path.join(global.output_dir, `screenshot_${sec}.png`)), null, 'file does not exists'); + }); + }); + + describe('#runOnIOS, #runOnAndroid, #runInWeb', () => { + it('should use Android locators', async () => { + await app.resetApp(); + await app.waitForElement('~startUserRegistrationCD', smallWait); + await app.click({ android: '~startUserRegistrationCD', ios: 'fake-element' }); + await app.see('Welcome to register a new User'); + }); + + it('should execute only on Android @quick', () => { + let platform = null; + app.runOnIOS(() => { + platform = 'ios'; + }); + app.runOnAndroid(() => { + platform = 'android'; + }); + app.runOnAndroid({ platformVersion: '7.0' }, () => { + platform = 'android7'; + }); + + assert.equal('android', platform); + }); + + it('should execute only on Android >= 5.0 @quick', () => { + app.runOnAndroid(caps => caps.platformVersion >= 5, () => {}); + }); + + it('should execute only in Web', () => { + app.isWeb = true; + let executed = false; + app.runOnIOS(() => { + executed = true; + }); + assert.ok(!executed); + }); + }); +});