Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,9 @@ fastlane/test_output
fastlane/README.md

test-ci-secrets.json

# appium
appium/node_modules
appium/package-lock.json
appium/tmp
appium/.env
16 changes: 16 additions & 0 deletions appium/README.md
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
Copy link
Collaborator

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)

Copy link
Contributor Author

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


##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
9 changes: 9 additions & 0 deletions appium/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
presets: [
[ '@babel/preset-env', {
targets: {
node: 'current',
},
} ],
]
};
33 changes: 33 additions & 0 deletions appium/config/wdio.ios.app.conf.js
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',
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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;
45 changes: 45 additions & 0 deletions appium/config/wdio.shared.conf.js
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)
}
}
};
37 changes: 37 additions & 0 deletions appium/package.json
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"
}
}
2 changes: 2 additions & 0 deletions appium/tests/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const DEFAULT_TIMEOUT = 15000;

10 changes: 10 additions & 0 deletions appium/tests/data/index.ts
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'
}
};
49 changes: 49 additions & 0 deletions appium/tests/helpers/ElementHelper.ts
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;
13 changes: 13 additions & 0 deletions appium/tests/screenobjects/all-screens.ts
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
};
22 changes: 22 additions & 0 deletions appium/tests/screenobjects/base.screen.ts
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,
});
}
}
53 changes: 53 additions & 0 deletions appium/tests/screenobjects/create-key.screen.ts
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();
45 changes: 45 additions & 0 deletions appium/tests/screenobjects/email.screen.ts
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();
22 changes: 22 additions & 0 deletions appium/tests/screenobjects/inbox.screen.ts
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();
Loading