From fb84f8952278c69f04584f04d2ec630598b30015 Mon Sep 17 00:00:00 2001 From: kobenguyent Date: Wed, 1 Nov 2023 14:11:27 +0100 Subject: [PATCH 1/5] 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); + }); + }); +}); From 0cfdb68c192a1bea2d60e77e5229fb3da043acfb Mon Sep 17 00:00:00 2001 From: kobenguyent Date: Wed, 1 Nov 2023 14:17:45 +0100 Subject: [PATCH 2/5] fix: deprecate appium 1.x --- .github/workflows/appium.yml | 58 ------------------------------------ package.json | 4 +-- 2 files changed, 2 insertions(+), 60 deletions(-) delete mode 100644 .github/workflows/appium.yml diff --git a/.github/workflows/appium.yml b/.github/workflows/appium.yml deleted file mode 100644 index 25c3bfca3..000000000 --- a/.github/workflows/appium.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Appium Tests - -on: - push: - branches: - - 3.x - - -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/package.json b/package.json index c5bc397a8..7e2749809 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,8 @@ "test:unit": "mocha test/unit --recursive --timeout 10000", "test:runner": "mocha test/runner --recursive --timeout 10000", "test": "npm run test:unit && npm run test:runner", - "test:appium-quick": "mocha test/helper/Appium_test.js --grep 'quick'", - "test:appium-other": "mocha test/helper/Appium_test.js --grep 'second'", + "test:appium-quick": "mocha test/helper/AppiumV2_test.js --grep 'quick'", + "test:appium-other": "mocha test/helper/AppiumV2_test.js --grep 'second'", "test-app:start": "php -S 127.0.0.1:8000 -t test/data/app", "test-app:stop": "kill -9 $(lsof -t -i:8000)", "test:unit:webbapi:playwright": "mocha test/helper/Playwright_test.js", From fec2c2929202ad57e36639ed5ac3d143c248c57d Mon Sep 17 00:00:00 2001 From: kobenguyent Date: Wed, 1 Nov 2023 14:27:03 +0100 Subject: [PATCH 3/5] fix: deprecate appium 1.x --- test/helper/AppiumV2Web_test.js | 4 +++- test/helper/AppiumV2_test.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/helper/AppiumV2Web_test.js b/test/helper/AppiumV2Web_test.js index ae61a022f..67e0e050e 100644 --- a/test/helper/AppiumV2Web_test.js +++ b/test/helper/AppiumV2Web_test.js @@ -15,7 +15,9 @@ describe('Appium Web', function () { browser: 'chrome', restart: false, desiredCapabilities: { - appiumVersion: '2.0.0', + 'sauce:options': { + appiumVersion: '2.0.0', + }, recordVideo: 'false', recordScreenshots: 'false', platformName: 'Android', diff --git a/test/helper/AppiumV2_test.js b/test/helper/AppiumV2_test.js index 29a76cb36..22e00e826 100644 --- a/test/helper/AppiumV2_test.js +++ b/test/helper/AppiumV2_test.js @@ -21,7 +21,9 @@ describe('Appium', function () { app: apk_path, appiumV2: true, desiredCapabilities: { - appiumVersion: '2.0.0', + 'sauce:options': { + appiumVersion: '2.0.0', + }, browserName: '', recordVideo: 'false', recordScreenshots: 'false', From f24f56800dff1343280ff0ced49b4c8c6479b216 Mon Sep 17 00:00:00 2001 From: kobenguyent Date: Thu, 2 Nov 2023 13:26:26 +0100 Subject: [PATCH 4/5] fix: add ios appium tests --- .../{appiumV2.yml => appiumV2_Android.yml} | 2 +- .github/workflows/appiumV2_iOS.yml | 59 +++++ package.json | 2 + test/helper/AppiumV2_ios_test.js | 202 ++++++++++++++++++ 4 files changed, 264 insertions(+), 1 deletion(-) rename .github/workflows/{appiumV2.yml => appiumV2_Android.yml} (97%) create mode 100644 .github/workflows/appiumV2_iOS.yml create mode 100644 test/helper/AppiumV2_ios_test.js diff --git a/.github/workflows/appiumV2.yml b/.github/workflows/appiumV2_Android.yml similarity index 97% rename from .github/workflows/appiumV2.yml rename to .github/workflows/appiumV2_Android.yml index 50ea3a935..d0b92b182 100644 --- a/.github/workflows/appiumV2.yml +++ b/.github/workflows/appiumV2_Android.yml @@ -1,4 +1,4 @@ -name: Appium V2 Tests +name: Appium V2 Tests - Android on: push: diff --git a/.github/workflows/appiumV2_iOS.yml b/.github/workflows/appiumV2_iOS.yml new file mode 100644 index 000000000..3dbe3aca1 --- /dev/null +++ b/.github/workflows/appiumV2_iOS.yml @@ -0,0 +1,59 @@ +name: Appium V2 Tests - iOS + +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:ios: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:ios:appium-other' + env: # Or as an environment variable + SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} + SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} diff --git a/package.json b/package.json index 7e2749809..010d04a92 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,8 @@ "test": "npm run test:unit && npm run test:runner", "test:appium-quick": "mocha test/helper/AppiumV2_test.js --grep 'quick'", "test:appium-other": "mocha test/helper/AppiumV2_test.js --grep 'second'", + "test:ios:appium-quick": "mocha test/helper/AppiumV2_ios_test.js --grep 'quick'", + "test:ios:appium-other": "mocha test/helper/AppiumV2_ios_test.js --grep 'second'", "test-app:start": "php -S 127.0.0.1:8000 -t test/data/app", "test-app:stop": "kill -9 $(lsof -t -i:8000)", "test:unit:webbapi:playwright": "mocha test/helper/Playwright_test.js", diff --git a/test/helper/AppiumV2_ios_test.js b/test/helper/AppiumV2_ios_test.js new file mode 100644 index 000000000..5595cc8e6 --- /dev/null +++ b/test/helper/AppiumV2_ios_test.js @@ -0,0 +1,202 @@ +const assert = require('assert'); +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; +// iOS test app is built from https://github.com/appium/ios-test-app and uploaded to Saucelabs +const apk_path = 'storage:filename=TestApp-iphonesimulator.zip'; +const smallWait = 3; + +describe('Appium iOS Tests', function () { + this.timeout(0); + + before(async () => { + global.codecept_dir = path.join(__dirname, '/../data'); + app = new Appium({ + app: apk_path, + appiumV2: true, + desiredCapabilities: { + 'sauce:options': { + appiumVersion: '2.0.0', + }, + browserName: '', + recordVideo: 'false', + recordScreenshots: 'false', + platformName: 'iOS', + platformVersion: '12.2', + deviceName: 'iPhone 8 Simulator', + androidInstallTimeout: 90000, + appWaitDuration: 300000, + }, + restart: true, + protocol: 'http', + host: 'ondemand.saucelabs.com', + port: 80, + 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 : #removeApp', () => { + describe( + '#grabAllContexts, #grabContext, #grabOrientation, #grabSettings', + () => { + it('should grab all available contexts for screen', async () => { + await app.resetApp(); + const val = await app.grabAllContexts(); + assert.deepEqual(val, ['NATIVE_APP']); + }); + + it('should grab current context', async () => { + const val = await app.grabContext(); + assert.equal(val, 'NATIVE_APP'); + }); + + it('should grab custom settings', async () => { + const val = await app.grabSettings(); + assert.deepEqual(val, { imageElementTapStrategy: 'w3cActions' }); + }); + }, + ); + }); + + describe('device orientation : #seeOrientationIs #setOrientation', () => { + it('should return correct status about device orientation', 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'); + } + }); + }); + + describe('#hideDeviceKeyboard', () => { + it('should hide device Keyboard @quick', async () => { + await app.resetApp(); + await app.click('~IntegerA'); + try { + await app.click('~locationStatus'); + } catch (e) { + e.message.should.include('element'); + } + await app.hideDeviceKeyboard('pressKey', 'Done'); + await app.click('~locationStatus'); + }); + + 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('see text : #see', () => { + it('should work inside elements @second', async () => { + await app.resetApp(); + await app.see('Compute Sum', '~ComputeSumButton'); + }); + }); + + describe('#appendField', () => { + it('should be able to send special keys to element @second', async () => { + await app.resetApp(); + await app.waitForElement('~IntegerA', smallWait); + await app.click('~IntegerA'); + await app.appendField('~IntegerA', '1'); + await app.hideDeviceKeyboard('pressKey', 'Done'); + await app.see('1', '~IntegerA'); + }); + }); + + describe('#waitForText', () => { + it('should return error if not present', async () => { + try { + await app.waitForText('Nothing here', 1, '~IntegerA'); + } catch (e) { + e.message.should.contain('element (~IntegerA) is not in DOM or there is no element(~IntegerA) with text "Nothing here" after 1 sec'); + } + }); + }); + + describe('#seeNumberOfElements @second', () => { + it('should return 1 as count', async () => { + await app.resetApp(); + await app.seeNumberOfElements('~IntegerA', 1); + }); + }); + + describe('see element : #seeElement, #dontSeeElement', () => { + it('should check visible elements on page @quick', async () => { + await app.resetApp(); + await app.seeElement('~IntegerA'); + 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('~ComputeSumButton'); + await app.see('0'); + }); + }); + + describe('#fillField @second', () => { + it('should fill field by accessibility id', async () => { + await app.resetApp(); + await app.waitForElement('~IntegerA', smallWait); + await app.click('~IntegerA'); + await app.fillField('~IntegerA', '1'); + await app.hideDeviceKeyboard('pressKey', 'Done'); + await app.see('1', '~IntegerA'); + }); + }); + + describe('#grabTextFrom, #grabValueFrom, #grabAttributeFrom @quick', () => { + it('should grab text from page', async () => { + await app.resetApp(); + const val = await app.grabTextFrom('~ComputeSumButton'); + assert.equal(val, 'Compute Sum'); + }); + + it('should grab attribute from element', async () => { + await app.resetApp(); + const val = await app.grabAttributeFrom('~ComputeSumButton', 'label'); + assert.equal(val, 'Compute Sum'); + }); + + it('should be able to grab elements', async () => { + await app.resetApp(); + const id = await app.grabNumberOfVisibleElements('~ComputeSumButton'); + assert.strictEqual(1, id); + }); + }); + + describe('#saveScreenshot', () => { + 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'); + }); + }); +}); From fd169626d107120c35874934f1e6c0e51b35917c Mon Sep 17 00:00:00 2001 From: kobenguyent Date: Thu, 2 Nov 2023 13:40:15 +0100 Subject: [PATCH 5/5] fix(docs): update read me --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4005f184e..1f95cee7c 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,14 @@ Build Status: +Appium Helper: +[![Appium V2 Tests - Android](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appiumV2_Android.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appiumV2_Android.yml) +[![Appium V2 Tests - iOS](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appiumV2_iOS.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appiumV2_iOS.yml) + +Web Helper: [![Playwright Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/playwright.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/playwright.yml) [![Puppeteer Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml) [![WebDriver Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml) -[![Appium Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appium.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appium.yml) [![TestCafe Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/testcafe.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/testcafe.yml) # CodeceptJS [![Made in Ukraine](https://img.shields.io/badge/made_in-ukraine-ffd700.svg?labelColor=0057b7)](https://stand-with-ukraine.pp.ua)