-
Notifications
You must be signed in to change notification settings - Fork 11
Feature/appium #645
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/appium #645
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # appium project | ||
|
|
||
| Project to run Appium tests together with WebdriverIO for: | ||
|
|
||
| - iOS/Android Native Apps | ||
|
|
||
| ## Before running tests, please create a `./apps` directory, download the app and move the app file into that directory | ||
|
|
||
| ##Create .env file in appium root and set next variables in that file: | ||
| ACCOUNT_EMAIL= | ||
| ACCOUNT_PASSWORD= | ||
| PASS_PHRASE= | ||
|
|
||
| ## Install dependencies with `npm i` | ||
|
|
||
| ## Run smoke tests for iOS with `npm run ios.smoke`, all tests with tag #smoke will be included | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| module.exports = { | ||
| presets: [ | ||
| [ '@babel/preset-env', { | ||
| targets: { | ||
| node: 'current', | ||
| }, | ||
| } ], | ||
| ] | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| const { join } = require('path'); | ||
| const { config } = require('./wdio.shared.conf'); | ||
| const pathWdioConfig = require('path'); | ||
| require('dotenv').config({ path: pathWdioConfig.resolve(__dirname, '../.env') }); | ||
|
|
||
| config.suites = { | ||
| all: [ | ||
| './tests/specs/**/*.spec.ts' | ||
| ], | ||
| smoke: [ | ||
| './tests/specs/login/GmailLogin.spec.ts', | ||
| './tests/specs/inbox/ReadTextEmail.spec.ts' | ||
| ] | ||
| }; | ||
|
|
||
| config.capabilities = [ | ||
| { | ||
| platformName: 'iOS', | ||
| iosInstallPause: 5000, | ||
| deviceName: 'iPhone 11 Pro Max', | ||
| platformVersion: '14.5', | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would this run iOS 15 as well? We've just bumped app minimum ios requirement to 15.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, all we need is to replace platform version or even use ENV variable for that and run what you need, I will add ENV variable for this |
||
| automationName: 'XCUITest', | ||
| app: join(process.cwd(), './apps/FlowCrypt.app'), | ||
| newCommandTimeout: 10000, | ||
| wdaLaunchTimeout: 300000, | ||
| wdaConnectionTimeout: 600000, | ||
| wdaStartupRetries: 4, | ||
| wdaStartupRetryInterval: 120000, | ||
| resetOnSessionStartOnly: true | ||
| }, | ||
| ]; | ||
|
|
||
| exports.config = config; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| const { join } = require('path'); | ||
|
|
||
| exports.config = { | ||
|
|
||
| runner: 'local', | ||
| framework: 'jasmine', | ||
| jasmineNodeOpts: { | ||
| defaultTimeoutInterval: 300000, | ||
| requires: ['ts-node/register', 'tsconfig-paths/register'] | ||
| }, | ||
| sync: true, | ||
| logLevel: 'silent', | ||
| deprecationWarnings: true, | ||
| bail: 0, | ||
| waitforTimeout: 15000, | ||
| connectionRetryTimeout: 90000, | ||
| connectionRetryCount: 3, | ||
| maxInstancesPerCapability: 1, | ||
| reporters: ['spec', | ||
| ['allure', { | ||
| outputDir: './tmp', | ||
| disableWebdriverStepsReporting: true, | ||
| disableWebdriverScreenshotsReporting: false, | ||
| }] | ||
| ], | ||
| services: [ | ||
| ['appium', { | ||
| command : 'appium', | ||
| logPath : join(process.cwd(), './tmp') | ||
| }] | ||
| ], | ||
| port: 4723, | ||
| path: '/wd/hub', | ||
| specFileRetries: 1, | ||
| specFileRetriesDeferred: false, | ||
|
|
||
| afterTest: function (test, context, { error, result, duration, passed, retries }) { | ||
| if (error) { | ||
| const timestampNow = new Date().getTime().toString(); | ||
| const path = join(process.cwd(), './tmp'); | ||
| driver.saveScreenshot(`${path}/${timestampNow}.png`); | ||
| console.log("Screenshot of failed test was saved to " + path) | ||
| } | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| { | ||
| "name": "flowcrypt-appium", | ||
| "version": "1.0.0", | ||
| "description": "", | ||
| "main": "index.js", | ||
| "scripts": { | ||
| "ios.smoke": "./node_modules/.bin/wdio ./config/wdio.ios.app.conf.js --suite smoke" | ||
| }, | ||
| "author": "", | ||
| "license": "ISC", | ||
| "devDependencies": { | ||
| "@babel/cli": "^7.10.4", | ||
| "@babel/core": "^7.10.4", | ||
| "@babel/preset-env": "^7.10.4", | ||
| "@babel/register": "^7.10.4", | ||
| "@babel/traverse": "^7.10.4", | ||
| "@babel/types": "^7.10.4", | ||
| "@wdio/allure-reporter": "6.10.6", | ||
| "@wdio/appium-service": "6.10.11", | ||
| "@wdio/cli": "6.10.11", | ||
| "@wdio/jasmine-framework": "6.10.11", | ||
| "@wdio/local-runner": "6.10.13", | ||
| "@wdio/spec-reporter": "6.10.6", | ||
| "@wdio/sync": "^6.1.14", | ||
| "babel-eslint": "^10.1.0", | ||
| "dotenv": "^10.0.0", | ||
| "eslint-config-standard": "^16.0.1", | ||
| "eslint-plugin-import": "^2.22.1", | ||
| "eslint-plugin-node": "^11.1.0", | ||
| "eslint-plugin-promise": "^4.2.1", | ||
| "eslint-plugin-wdio": "^6.6.0", | ||
| "node-fetch": "^2.6.1", | ||
| "ts-node": "^9.1.1", | ||
| "typescript": "^4.1.3", | ||
| "webdriverio": "6.10.11" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export const DEFAULT_TIMEOUT = 15000; | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| export const CommonData = { | ||
| account: { | ||
| email: process.env.ACCOUNT_EMAIL, | ||
| password: process.env.ACCOUNT_PASSWORD, | ||
| passPhrase: process.env.PASS_PHRASE | ||
| }, | ||
| sender: { | ||
| email: 'dmitry@flowcrypt.com' | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import {DEFAULT_TIMEOUT} from "../constants"; | ||
|
|
||
| class ElementHelper { | ||
|
|
||
| /** | ||
| * returns true or false for element visibility | ||
| */ | ||
| static elementDisplayed(element): boolean { | ||
| return element.isExisting(); | ||
| } | ||
|
|
||
| static waitElementVisible(element, timeout: number = DEFAULT_TIMEOUT) { | ||
| try { | ||
| element.waitForDisplayed({timeout: timeout}); | ||
| } catch (error) { | ||
| throw new Error(`Element isn't visible after ${timeout} seconds. Error: ${error}`); | ||
| } | ||
| } | ||
|
|
||
| static waitElementInvisible(element, timeout: number = DEFAULT_TIMEOUT) { | ||
| try { | ||
| element.waitForDisplayed({timeout: timeout, reverse: true}); | ||
| } catch (error) { | ||
| throw new Error(`Element still visible after ${timeout} seconds. Error: ${error}`); | ||
| } | ||
| } | ||
|
|
||
| static staticText(label: string) { | ||
| const selector = `**/XCUIElementTypeStaticText[\`label == "${label}"\`]`; | ||
| return $(`-ios class chain:${selector}`); | ||
| } | ||
|
|
||
| static staticTextContains(label: string) { | ||
| const selector = `**/XCUIElementTypeStaticText[\`label CONTAINS "${label}"\`]`; | ||
| return $(`-ios class chain:${selector}`); | ||
| } | ||
|
|
||
| static clickStaticText (label: string) { | ||
| this.waitElementVisible(this.staticText(label)); | ||
| this.staticText(label).click(); | ||
| } | ||
|
|
||
| static doubleClick(element) { | ||
| this.waitElementVisible(element); | ||
| element.doubleClick(); | ||
| } | ||
| } | ||
|
|
||
| export default ElementHelper; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import SplashScreen from './splash.screen'; | ||
| import CreateKeyScreen from './create-key.screen'; | ||
| import InboxScreen from './inbox.screen'; | ||
| import MenuBarScreen from './menu-bar.screen'; | ||
| import EmailScreen from './email.screen' | ||
|
|
||
| export { | ||
| SplashScreen, | ||
| CreateKeyScreen, | ||
| InboxScreen, | ||
| MenuBarScreen, | ||
| EmailScreen | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { DEFAULT_TIMEOUT } from '../constants'; | ||
|
|
||
| export default class BaseScreen { | ||
|
|
||
| locator: string; | ||
| constructor (selector: string) { | ||
| this.locator = selector; | ||
| } | ||
|
|
||
| /** | ||
| * Wait for screen to be visible | ||
| * | ||
| * @param {boolean} isShown | ||
| * @return {boolean} | ||
| */ | ||
| waitForScreen (isShown: boolean = true) { | ||
| return $(this.locator).waitForDisplayed({ | ||
| timeout: DEFAULT_TIMEOUT, | ||
| reverse: !isShown, | ||
| }); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import BaseScreen from './base.screen'; | ||
| import {CommonData} from '../data'; | ||
|
|
||
| const SELECTORS = { | ||
| SET_PASS_PHRASE_BUTTON: '~Set pass phrase', | ||
| ENTER_YOUR_PASS_PHRASE_FIELD: '-ios class chain:**/XCUIElementTypeSecureTextField[`value == "Enter your pass phrase"`]', | ||
| OK_BUTTON: '~Ok', | ||
| CONFIRM_PASS_PHRASE_FIELD: '~textField', | ||
| }; | ||
|
|
||
| class CreateKeyScreen extends BaseScreen { | ||
| constructor () { | ||
| super(SELECTORS.SET_PASS_PHRASE_BUTTON); | ||
| } | ||
|
|
||
| get setPassPhraseButton () { | ||
| return $(SELECTORS.SET_PASS_PHRASE_BUTTON); | ||
| } | ||
|
|
||
| get enterPassPhraseField () { | ||
| return $(SELECTORS.ENTER_YOUR_PASS_PHRASE_FIELD); | ||
| } | ||
|
|
||
| get okButton () { | ||
| return $(SELECTORS.OK_BUTTON) | ||
| } | ||
|
|
||
| get confirmPassPhraseField () { | ||
| return $(SELECTORS.CONFIRM_PASS_PHRASE_FIELD) | ||
| } | ||
|
|
||
| fillPassPhrase (passPhrase: string) { | ||
| this.enterPassPhraseField.setValue(passPhrase); | ||
| } | ||
|
|
||
| clickSetPassPhraseBtn () { | ||
| this.setPassPhraseButton.click(); | ||
| } | ||
|
|
||
| confirmPassPhrase (passPhrase: string) { | ||
| this.confirmPassPhraseField.click(); | ||
| this.confirmPassPhraseField.setValue(passPhrase); | ||
| this.okButton.click(); | ||
| } | ||
|
|
||
| setPassPhrase(text: string = CommonData.account.passPhrase) { | ||
| this.fillPassPhrase(text); | ||
| this.clickSetPassPhraseBtn(); | ||
| this.confirmPassPhrase(text); | ||
| } | ||
| } | ||
|
|
||
| export default new CreateKeyScreen(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import BaseScreen from './base.screen'; | ||
|
|
||
| const SELECTORS = { | ||
| BACK_BTN: '~arrow left c' | ||
| }; | ||
|
|
||
| const { join } = require('path'); | ||
|
|
||
| class EmailScreen extends BaseScreen { | ||
| constructor () { | ||
| super(SELECTORS.BACK_BTN); | ||
| } | ||
|
|
||
| get backButton() { | ||
| return $(SELECTORS.BACK_BTN) | ||
| } | ||
|
|
||
| checkEmailAddress (email) { | ||
| const selector = `~${email}`; | ||
| $(selector).waitForDisplayed(); | ||
| } | ||
|
|
||
| checkEmailSubject (subject) { | ||
| const selector = `~${subject}`; | ||
| $(selector).waitForDisplayed(); | ||
| } | ||
|
|
||
| checkEmailText (text) { | ||
| const selector = `~${text}`; | ||
| $(selector).waitForDisplayed(); | ||
| } | ||
|
|
||
| checkOpenedEmail (email, subject, text) { | ||
| this.backButton.waitForDisplayed(); | ||
| this.checkEmailAddress(email); | ||
| this.checkEmailSubject(subject); | ||
| this.checkEmailText(text); | ||
| } | ||
|
|
||
| clickBackButton () { | ||
| this.backButton.click(); | ||
| } | ||
| } | ||
|
|
||
| export default new EmailScreen(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import BaseScreen from './base.screen'; | ||
|
|
||
| const SELECTORS = { | ||
| ENTER_YOUR_PASS_PHRASE_FIELD: '-ios class chain:**/XCUIElementTypeSecureTextField[`value == "Enter your pass phrase"`]', | ||
| OK_BUTTON: '~Ok', | ||
| CONFIRM_PASS_PHRASE_FIELD: '~textField', | ||
|
|
||
| }; | ||
|
|
||
| class InboxScreen extends BaseScreen { | ||
| constructor () { | ||
| super(SELECTORS.CONFIRM_PASS_PHRASE_FIELD); | ||
| } | ||
| clickOnUserEmail (email) { | ||
| const selector = `~${email}`; | ||
| $(selector).click(); | ||
| } | ||
|
|
||
|
|
||
| } | ||
|
|
||
| export default new InboxScreen(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could there be a command to automate this? Maybe using fastlane? To create a build and immediately test it (it will be needed for CI anyway)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, we could make comand to copy .app file