...
 
Commits (11)
......@@ -68,9 +68,19 @@ jobs:
- restore_cache:
key: node-v1-{{ checksum "package.json" }}-{{ arch }}
# remove detox from CI until is fixed
# - run:
# name: Install detox
# command:
# |
# HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew
# HOMEBREW_NO_AUTO_UPDATE=1 brew install --HEAD applesimutils
# npm install -g detox-cli
# npm install -g detox
# not using a workspace here as Node and Yarn versions
# differ between our macOS executor image and the Docker containers above
- run: yarn install
- run: yarn install --frozen-lockfile
- save_cache:
key: yarn-v1-{{ checksum "yarn.lock" }}-{{ arch }}
......@@ -107,6 +117,11 @@ jobs:
paths:
- ios/Pods
# remove detox from CI until is fixed
# Run e2e
# - run: detox build -c ios.sim.release
# - run: detox test -c ios.sim.release --cleanup
### TODO- get tests running with fastlane
......
......@@ -36,29 +36,6 @@ build:android:
expire_in: 7 days
when: on_success
e2e:browserstacks:
image: node:10.16.3
stage: e2e
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
script:
- yarn install
- export bsAPP=`curl -u "${bsUSER}:${bsKEY}" -X POST "https://api-cloud.browserstack.com/app-automate/upload" -F "file=@./Minds-$CI_BUILD_REF_SLUG.apk"| grep -o 'bs\:\/\/.*"' | sed 's/.$//'`
- yarn e2e
tags:
- docker
dependencies:
- build:android
only:
refs:
- /^stable-*/
- /^release-*/
- /^feat-*/
- /^test-*/
allow_failure: true
deploy:s3:
image: minds/ci:latest
stage: deploy
......
......@@ -37,5 +37,18 @@
- `yarn test`
## Testing e2e (macOS)
Install the detox cli
- `brew tap wix/brew`
- `brew install applesimutils`
- `yarn global add detox-cli`
Run the tests
- `detox build -c ios.sim.debug`
- `detox test -c ios.sim.debug`
You can use -c ios.sim.release for e2e test a production build
### _Copyright Minds 2018_
import wd from 'wd';
import sleep from '../../src/common/helpers/sleep';
export default async(driver) => {
// should ask for permissions
const permmision = await driver.waitForElementById('com.android.packageinstaller:id/permission_allow_button', wd.asserters.isDisplayed, 10000)
// we accept
permmision.click();
}
\ No newline at end of file
import wd from 'wd';
import sleep from '../../src/common/helpers/sleep';
export default async(driver) => {
// select first image
const firstImage = await driver.waitForElementByAccessibilityId('Gallery image/jpeg', wd.asserters.isDisplayed, 5000);
await firstImage.click();
await sleep(3000);
}
\ No newline at end of file
import wd from 'wd';
import sleep from '../../src/common/helpers/sleep';
export default async(driver, amount) => {
const lockButton = await driver.waitForElementByAccessibilityId('Post lock button', wd.asserters.isDisplayed, 5000);
await lockButton.click();
const postInput = await driver.waitForElementByAccessibilityId('Poster lock amount input', wd.asserters.isDisplayed, 5000);
await postInput.type(amount);
// we press post button
const postButton = await driver.elementByAccessibilityId('Poster lock done button');
await postButton.click();
}
\ No newline at end of file
export default async(driver) => {
const username = await driver.elementByAccessibilityId('username input');
const password = await driver.elementByAccessibilityId('password input');
const loginButton = await driver.elementByAccessibilityId('login button');
await username.type(process.env.loginUser);
await password.type(process.env.loginPass);
await loginButton.click();
}
\ No newline at end of file
import wd from 'wd';
import sleep from '../../src/common/helpers/sleep';
export default async(driver, text) => {
// post screen must be shown
const postInput = await driver.waitForElementByAccessibilityId('PostInput', wd.asserters.isDisplayed, 5000);
await postInput.type(text);
// we press post button
const postButton = await driver.elementByAccessibilityId('Capture Post Button');
await postButton.click();
}
\ No newline at end of file
import wd from 'wd';
import sleep from '../../src/common/helpers/sleep';
export default async(driver) => {
// tap the capture button
const button = await driver.waitForElementByAccessibilityId('CaptureButton', wd.asserters.isDisplayed, 10000);
button.click();
return button;
}
\ No newline at end of file
import wd from 'wd';
import sleep from '../../src/common/helpers/sleep';
export default async(driver, options) => {
// tap the toggle button
const button = await driver.waitForElementByAccessibilityId('NSFW button', wd.asserters.isDisplayed, 5000);
await button.click();
// wait until the menu is shown
await sleep(500);
for (let index = 0; index < options.length; index++) {
const name = options[index];
const element = await driver.elementByAccessibilityId(`NSFW ${name}`);
await element.click();
}
let action = new wd.TouchAction(driver);
action.tap({x:100, y:170});
await action.release().perform();
}
\ No newline at end of file
import wd from 'wd';
import reporterFactory from '../tests-helpers/browserstack-reporter.factory';
import post from './actions/post';
import login from './actions/login';
import { driver, capabilities} from './config';
import sleep from '../src/common/helpers/sleep';
import pressCapture from './actions/pressCapture';
import acceptPermissions from './actions/acceptPermissions';
import attachPostGalleryImage from './actions/attachPostGalleryImage';
import selectNsfw from './actions/selectNsfw';
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
const data = {sessiondID: null};
jasmine.getEnv().addReporter(reporterFactory(data));
//TODO: add support for ios to this test (xpath)
describe('Activity flow tests', () => {
beforeAll(async () => {
await driver.init(capabilities);
data.sessiondID = await driver.getSessionId();
console.log('BROWSERSTACK_SESSION: ' + data.sessiondID);
await driver.waitForElementByAccessibilityId('username input', wd.asserters.isDisplayed, 5000);
// we should login
await login(driver);
});
afterAll(async () => {
await driver.quit();
});
it('should post a text and see it in the newsfeed', async () => {
const str = 'My e2e activity';
// press capture button
await pressCapture(driver);
// accept gallery permissions
await acceptPermissions(driver);
// make the post
await post(driver, str);
// should post and return to the newsfeed
await driver.waitForElementByAccessibilityId('Newsfeed Screen', wd.asserters.isDisplayed, 10000);
// the first element of the list should be the post
const textElement = await driver.waitForElementByXPath('//android.view.ViewGroup[@content-desc="Newsfeed Screen"]/android.view.ViewGroup[1]/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[2]/android.view.ViewGroup/android.widget.TextView[2]');
expect(await textElement.text()).toBe(str);
});
it('should like the post', async() => {
const likeButton = await driver.waitForElementByAccessibilityId('Thumb up activity button', 5000);
await likeButton.click();
const likeCount = await driver.waitForElementByAccessibilityId('Thumb up count', 5000);
expect(await likeCount.text()).toBe('1');
});
it('should unlike the post', async() => {
const likeButton = await driver.waitForElementByAccessibilityId('Thumb down activity button', 5000);
await likeButton.click();
const likeCount = await driver.waitForElementByAccessibilityId('Thumb down count', 5000);
expect(await likeCount.text()).toBe('1');
});
});
\ No newline at end of file
import factory from '../tests-helpers/e2e-driver.factory';
const customCapabilities = {
'device' : 'Samsung Galaxy S9',
'os_version' : '8.0'
};
let driver, capabilities;
if (process.env.e2elocal) {
[driver, capabilities] = factory('androidLocal', {});
} else {
[driver, capabilities] = factory('browserStack', customCapabilities);
}
export {driver, capabilities} ;
import wd from 'wd';
import reporterFactory from '../tests-helpers/browserstack-reporter.factory';
import { driver, capabilities} from './config';
import post from './actions/post';
import login from './actions/login';
import sleep from '../src/common/helpers/sleep';
import pressCapture from './actions/pressCapture';
import acceptPermissions from './actions/acceptPermissions';
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
const data = {sessiondID: null};
jasmine.getEnv().addReporter(reporterFactory(data));
describe('Discovery post edit flow', () => {
beforeAll(async () => {
await driver.init(capabilities);
data.sessiondID = await driver.getSessionId();
console.log('BROWSERSTACK_SESSION: ' + data.sessiondID);
await driver.waitForElementByAccessibilityId('username input', wd.asserters.isDisplayed, 5000);
// we should login
await login(driver);
});
afterAll(async () => {
await driver.quit();
});
it('should post a text and go to discovery', async () => {
const str = 'My e2e post #mye2epost';
await pressCapture(driver);
await acceptPermissions(driver);
// make the post
await post(driver, str);
// move to discovery
const discoveryTab = await driver.waitForElementByAccessibilityId('Discovery tab button', wd.asserters.isDisplayed, 10000);
await discoveryTab.click();
});
it('should search for the post', async () => {
// select all list
const all = await driver.waitForElementByAccessibilityId('Discovery All', wd.asserters.isDisplayed, 5000);
await all.click();
await sleep(500);
// select latest filter
const latest = await driver.waitForElementByAccessibilityId('Filter latest button', wd.asserters.isDisplayed, 1000);
await latest.click();
await sleep(500);
// search the post
const search = await driver.waitForElementByAccessibilityId('Discovery Search Input', wd.asserters.isDisplayed, 10000);
await search.type('mye2epost');
await sleep(4000);
// activity menu button
const activityMenu = await driver.waitForElementByAccessibilityId('Activity Menu button', wd.asserters.isDisplayed, 5000);
await activityMenu.click();
await sleep(500);
// tap edit
const edit = await driver.waitForElementByAndroidUIAutomator('new UiSelector().text("Edit")', wd.asserters.isDisplayed, 5000);
await edit.click();
// change the text
const editorInput = await driver.waitForElementByAccessibilityId('Post editor input', wd.asserters.isDisplayed, 5000);
await editorInput.type(' edited!');
// tap save
const save = await driver.waitForElementByAccessibilityId('Post editor save button', wd.asserters.isDisplayed, 5000);
await save.click();
// confirm activity text changed
await driver.waitForElementByAndroidUIAutomator('new UiSelector().text("My e2e post #mye2epost edited!")', wd.asserters.isDisplayed, 5000);
});
});
\ No newline at end of file
import wd from 'wd';
import reporterFactory from '../tests-helpers/browserstack-reporter.factory';
import { driver, capabilities} from './config';
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
const data = {sessiondID: null};
jasmine.getEnv().addReporter(reporterFactory(data));
describe('Login flow', () => {
let username, password, loginButton;
beforeAll(async () => {
await driver.init(capabilities);
data.sessiondID = await driver.getSessionId();
console.log('BROWSERSTACK_SESSION: ' + data.sessiondID);
await driver.waitForElementByAccessibilityId('username input', wd.asserters.isDisplayed, 5000);
});
afterAll(async () => {
await driver.quit();
});
it('should shows login error on wrong credentials', async () => {
expect(await driver.hasElementByAccessibilityId('username input')).toBe(true);
expect(await driver.hasElementByAccessibilityId('password input')).toBe(true);
username = await driver.elementByAccessibilityId('username input');
await username.type('myuser');
password = await driver.elementByAccessibilityId('password input');
await password.type('mypass');
loginButton = await driver.elementByAccessibilityId('login button');
await loginButton.click();
// message should appear
await driver.waitForElementByAccessibilityId('loginMsg', wd.asserters.isDisplayed, 5000);
const textElement = await driver.elementByAccessibilityId('loginMsg');
expect(await textElement.text()).toBe('The user credentials were incorrect.');
});
it('should go to newsfeed on successful login', async () => {
// try successfull login
await username.type(process.env.loginUser);
await password.type(process.env.loginPass);
await loginButton.click();
// should open the newsfeed
await driver.waitForElementByAccessibilityId('Newsfeed Screen', wd.asserters.isDisplayed, 5000);
});
it('should go to login after logout', async () => {
const menu = await driver.elementByAccessibilityId('Main menu button');
// tap menu button
await menu.click();
const logout = await driver.waitForElementByAccessibilityId('Logout', wd.asserters.isDisplayed, 5000);
// tap logout
logout.click();
await driver.waitForElementByAccessibilityId('username input', wd.asserters.isDisplayed, 5000);
})
});
\ No newline at end of file
import wd from 'wd';
import reporterFactory from '../tests-helpers/browserstack-reporter.factory';
import post from './actions/post';
import login from './actions/login';
import { driver, capabilities} from './config';
import sleep from '../src/common/helpers/sleep';
import pressCapture from './actions/pressCapture';
import acceptPermissions from './actions/acceptPermissions';
import attachPostGalleryImage from './actions/attachPostGalleryImage';
import selectNsfw from './actions/selectNsfw';
import lockPost from './actions/lockPost';
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
const data = {sessiondID: null};
jasmine.getEnv().addReporter(reporterFactory(data));
//TODO: add support for ios to this test (xpath)
describe('Post flow tests', () => {
beforeAll(async () => {
await driver.init(capabilities);
data.sessiondID = await driver.getSessionId();
console.log('BROWSERSTACK_SESSION: ' + data.sessiondID);
await driver.waitForElementByAccessibilityId('username input', wd.asserters.isDisplayed, 5000);
// we should login
await login(driver);
});
afterAll(async () => {
await driver.quit();
});
it('should post a text and see it in the newsfeed', async () => {
const str = 'My e2e post #mye2epost';
// press capture button
await pressCapture(driver);
// accept gallery permissions
await acceptPermissions(driver);
// make the post
await post(driver, str);
// should post and return to the newsfeed
await driver.waitForElementByAccessibilityId('Newsfeed Screen', wd.asserters.isDisplayed, 10000);
// the first element of the list should be the post
const textElement = await driver.waitForElementByXPath('//android.view.ViewGroup[@content-desc="Newsfeed Screen"]/android.view.ViewGroup[1]/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[2]/android.view.ViewGroup/android.widget.TextView[2]');
expect(await textElement.text()).toBe(str);
});
it('should remind the previuos post and see it in the newsfeed', async () => {
const str = 'Reminding my own post';
const remindButton = await driver.waitForElementByAccessibilityId('Remind activity button', 5000);
// tap remind
await remindButton.click();
// make the post
await post(driver, str);
// should post and return to the newsfeed
await driver.waitForElementByAccessibilityId('Newsfeed Screen', wd.asserters.isDisplayed, 10000);
// the first element of the list should be the post
const textElement = await driver.waitForElementByXPath('//android.view.ViewGroup[@content-desc="Newsfeed Screen"]/android.view.ViewGroup[1]/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[2]/android.view.ViewGroup/android.widget.TextView[2]');
expect(await textElement.text()).toBe(str);
});
it('should post a nsfw and see it in the newsfeed', async () => {
const str = 'My e2e post #mye2epost';
// press capture button
await pressCapture(driver);
// select nsfw
await selectNsfw(driver, ['Nudity', 'Pornography']);
// make the post
await post(driver, str);
// should post and return to the newsfeed
await driver.waitForElementByAccessibilityId('Newsfeed Screen', wd.asserters.isDisplayed, 10000);
// the first element of the list should be the post
const textElement = await driver.waitForElementByXPath('//android.view.ViewGroup[@content-desc="Newsfeed Screen"]/android.view.ViewGroup[1]/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[2]/android.view.ViewGroup/android.widget.TextView[2]');
expect(await textElement.text()).toBe(str);
});
it('should post paywalled content', async () => {
// press capture button
await pressCapture(driver);
// deselect nsfw
await selectNsfw(driver, ['Nudity', 'Pornography']);
const str = 'pay me something';
await lockPost(driver, '1');
// make the post with image and no permissions wait
await post(driver, str);
await sleep(1000);
const textElement = await driver.waitForElementByXPath('//android.view.ViewGroup[@content-desc="Newsfeed Screen"]/android.view.ViewGroup[1]/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[2]/android.view.ViewGroup/android.widget.TextView[3]');
expect(await textElement.text()).toBe('Locked');
});
it('should post an image and see it in the newsfeed', async () => {
const str = 'My e2e post image #mye2epostimage';
// press capture button
await pressCapture(driver);
// attach image
await attachPostGalleryImage(driver);
// make the post with image and no permissions wait
await post(driver, str);
// should post and return to the newsfeed
await driver.waitForElementByAccessibilityId('Newsfeed Screen', wd.asserters.isDisplayed, 10000);
// the first element of the list should be the post
const textElement = await driver.waitForElementByXPath('//android.view.ViewGroup[@content-desc="Newsfeed Screen"]/android.view.ViewGroup[1]/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[2]/android.view.ViewGroup/android.widget.TextView[2]');
expect(await textElement.text()).toBe(str);
});
it('should open the images in full screen after tap', async () => {
// get the Image touchable
const imageButton = await driver.waitForElementByAccessibilityId('Posted Image', wd.asserters.isDisplayed, 10000);
await imageButton.click();
const image = await driver.waitForElementByXPath('/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.widget.ImageView');
const goBack = await driver.waitForElementByAccessibilityId('Go back button', wd.asserters.isDisplayed, 10000);
await goBack.click();
});
it('should upload an image and cancel it', async () => {
// press capture button
await pressCapture(driver);
// attach image
await attachPostGalleryImage(driver);
await sleep(5000);
// we press post button
const deleteButton = await driver.elementByAccessibilityId('Attachment Delete Button');
await deleteButton.click();
await sleep(1000);
// should fail to find the delete button
return expect(driver.elementByAccessibilityId('Attachment Delete Button')).rejects.toHaveProperty('status', 7);
});
it('should return to the newsfeed', async () => {
// tap the back button
await driver.back();
// should open the newsfeed
await driver.waitForElementByAccessibilityId('Newsfeed Screen', wd.asserters.isDisplayed, 5000);
});
});
\ No newline at end of file
import wd from 'wd';
import reporterFactory from '../tests-helpers/browserstack-reporter.factory';
import login from './actions/login';
import { driver, capabilities} from './config';
import sleep from '../src/common/helpers/sleep';
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
const data = {sessiondID: null};
jasmine.getEnv().addReporter(reporterFactory(data));
//TODO: add support for ios to this test (xpath)
describe('Top-bar tests', () => {
beforeAll(async () => {
await driver.init(capabilities);
data.sessiondID = await driver.getSessionId();
console.log('BROWSERSTACK_SESSION: ' + data.sessiondID);
await driver.waitForElementByAccessibilityId('username input', wd.asserters.isDisplayed, 5000);
// we should login
await login(driver);
});
afterAll(async () => {
await driver.quit();
});
it('should open the boost console', async () => {
const button = await driver.waitForElementByAccessibilityId('boost-console button', wd.asserters.isDisplayed, 7000);
await button.click();
const textElement = await driver.waitForElementByXPath('/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup[1]/android.view.ViewGroup[2]/android.widget.TextView');
expect(await textElement.text()).toBe('Boost Console');
const back = await driver.waitForElementByXPath('//android.widget.Button[@content-desc="Go back"]/android.view.ViewGroup/android.widget.ImageView');
back.click();
});
it('should open the users profile on clicking the profile avatar', async () => {
const button = await driver.waitForElementByAccessibilityId('topbar avatar button', wd.asserters.isDisplayed, 5000);
await button.click();
await driver.waitForElementByXPath('/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup[1]/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[1]/android.view.ViewGroup[3]/android.widget.ImageView');
const back = await driver.waitForElementByXPath('/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup[1]/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[1]/android.view.ViewGroup[2]/android.widget.TextView'); back.click();
});
it('should open the menu when clicking the hamburger menu', async () => {
const button = await driver.waitForElementByAccessibilityId('Main menu button', wd.asserters.isDisplayed, 5000);
await button.click();
const logoutButton = await driver.waitForElementByXPath('//android.view.ViewGroup[@content-desc="Logout"]/android.widget.TextView[2]');
expect(await logoutButton.text()).toBe('Logout');
const back = await driver.waitForElementByXPath('//android.widget.Button[@content-desc="Go back"]/android.view.ViewGroup/android.widget.ImageView');
back.click();
});
});
......@@ -35,7 +35,7 @@ exports[`LoginForm component should renders correctly 1`] = `
},
]
}
testID="username input"
testID="usernameInput"
underlineColorAndroid="transparent"
value=""
/>
......@@ -67,7 +67,7 @@ exports[`LoginForm component should renders correctly 1`] = `
},
]
}
testID="password input"
testID="userPasswordInput"
underlineColorAndroid="transparent"
value=""
/>
......@@ -251,7 +251,7 @@ exports[`LoginForm component should renders correctly 1`] = `
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={null}
testID="login button"
testID="loginButton"
>
<View
pointerEvents="box-only"
......
/**
* Login action
* @param {string} username
* @param {string} password
*/
export default async function(username, password) {
await element(by.id('usernameInput')).typeText(username);
await element(by.id('userPasswordInput')).typeText(password);
await element(by.id('loginButton')).tap();
}
{
"setupFilesAfterEnv": ["./init.js"],
"testEnvironment": "node",
"reporters": ["detox/runners/jest/streamlineReporter"],
"verbose": true
}
const detox = require('detox');
const config = require('../package.json').detox;
const adapter = require('detox/runners/jest/adapter');
const specReporter = require('detox/runners/jest/specReporter');
const assignReporter = require('detox/runners/jest/assignReporter');
// Set the default timeout
jest.setTimeout(120000);
jasmine.getEnv().addReporter(adapter);
// This takes care of generating status logs on a per-spec basis. By default, jest only reports at file-level.
// This is strictly optional.
jasmine.getEnv().addReporter(specReporter);
// This will post which device has assigned to run a suite, which can be useful in a multiple-worker tests run.
// This is strictly optional.
jasmine.getEnv().addReporter(assignReporter);
beforeAll(async () => {
await detox.init(config, { launchApp: false });
});
beforeEach(async () => {
await adapter.beforeEach();
});
afterAll(async () => {
await adapter.afterAll();
await detox.cleanup();
});
import login from "./actions/login";
import sleep from '../src/common/helpers/sleep';
describe('Login Flow', () => {
beforeEach(async () => {
await device.launchApp({
newInstance: true,
permissions: {
notifications: 'YES',
},
});
});
it('should show error', async () => {
// should login successfully
await expect(element(by.id('usernameInput'))).toBeVisible();
// we moved the login logic to an action to avoid code duplication
await login('bad', 'credentials');
await sleep(1000);
// it should show the error message
// according to the detox docs it should be toHaveText but it only works with toHaveLabel
await expect(element(by.id('loginMsg'))).toHaveLabel('The user credentials were incorrect.');
});
it('should login successfully', async () => {
// should login successfully
await expect(element(by.id('usernameInput'))).toBeVisible();
// we moved the login logic to an action to avoid code duplication
await login(process.env.loginUser, process.env.loginPass);
// it should show the newsfeed screen
await expect(element(by.id('NewsfeedScreen'))).toBeVisible();
});
});
const config = {
"automock": false,
"cacheDirectory": ".jest/cache",
"testRegex": "./__e2e__/.*-test.js$",
"preset": "react-native",
"testPathIgnorePatterns": [
"/node_modules/"
],
"transformIgnorePatterns": [
"node_modules/(?!react-native|react-navigation|vargs)/"
],
"moduleNameMapper": {
"^image![a-zA-Z0-9$_-]+$": "GlobalImageStub",
"^[@./a-zA-Z0-9$_-]+\\.(png|gif)$": "RelativeImageStub"
},
"snapshotSerializers": [
"./node_modules/enzyme-to-json/serializer"
]
}
module.exports = config
\ No newline at end of file
{
"name": "Minds",
"version": "0.0.1",
"version": "3.12.0",
"private": true,
"scripts": {
"android": "react-native run-android",
......@@ -11,8 +11,6 @@
"update-settings": "ts-node tasks/update-settings.js",
"test": "jest",
"locale": "ts-node tasks/poeditor.js",
"e2e": "jest -c jest.e2e.config.js",
"e2e-local": "e2elocal=1 jest -c jest.e2e.config.js",
"preinstall": "git config core.hooksPath .githooks",
"postinstall": "jetify"
},
......@@ -91,6 +89,7 @@
"@react-native-community/eslint-config": "^0.0.5",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "^24.9.0",
"detox": "^14.7.1",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0",
"enzyme-to-json": "^3.4.0",
......@@ -103,5 +102,26 @@
"react-test-renderer": "16.9.0",
"ts-node": "^8.4.1",
"typescript": "^3.7.2"
},
"detox": {
"configurations": {
"ios.sim.debug": {
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/Minds.app",
"build": "xcodebuild -workspace ios/Minds.xcworkspace -scheme Minds -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"device": {
"type": "iPhone 11"
}
},
"ios.sim.release": {
"binaryPath": "ios/build/Build/Products/Release-iphonesimulator/Minds.app",
"build": "xcodebuild -workspace ios/Minds.xcworkspace -scheme Minds -configuration Release -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"device": {
"type": "iPhone 11"
}
}
},
"test-runner": "jest"
}
}
......@@ -10,6 +10,7 @@ import {
// TextInput,
StyleSheet,
KeyboardAvoidingView,
Platform,
} from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
......@@ -21,7 +22,6 @@ import { ComponentsStyle } from '../styles/Components';
import { Button } from 'react-native-elements'
import i18n from '../common/services/i18n.service';
import testID from '../common/helpers/testID';
import logService from '../common/services/log.service';
import ModalPicker from '../common/components/ModalPicker';
......@@ -55,8 +55,10 @@ export default class LoginForm extends Component {
* Render
*/
render() {
const msg = (this.state.msg) ? <Animatable.Text animation="bounceInLeft" style={[CommonStyle.colorLight, { textAlign: 'center' }]} {...testID('loginMsg')}>{this.state.msg}</Animatable.Text>:null;
console.log('IST',Platform.isTesting);
const msg = this.state.msg ? (
<Animatable.Text animation="bounceInLeft" style={[CommonStyle.colorLight, { textAlign: 'center' }]} testID="loginMsg">{this.state.msg}</Animatable.Text>
) : null;
const inputs = this.getInputs();
const buttons = this.getButtons();
......@@ -119,7 +121,7 @@ export default class LoginForm extends Component {
loadingRight={true}
disabled={this.state.inProgress}
disabledStyle={CommonStyle.backgroundTransparent}
{...testID('login button')}
testID="loginButton"
/>
]
......@@ -166,7 +168,7 @@ export default class LoginForm extends Component {
autoCapitalize={'none'}
value={this.state.username.trim()}
key={1}
{...testID('username input')}
testID="usernameInput"
/>,
<View key={2}>
<TextInput
......@@ -179,10 +181,14 @@ export default class LoginForm extends Component {
underlineColorAndroid='transparent'
onChangeText={(value) => this.setState({ password: value })}
value={this.state.password}
{...testID('password input')}
testID="userPasswordInput"
/>
<Icon
name={this.state.hidePassword ? 'md-eye' : 'md-eye-off'}
size={25}
style={ComponentsStyle.loginInputIcon}
onPress={this.toggleHidePassword}
/>
<Icon name={this.state.hidePassword ? 'md-eye' : 'md-eye-off'} size={25} style={ComponentsStyle.loginInputIcon} onPress={this.toggleHidePassword}/>
</View>
];
}
......
......@@ -18,7 +18,6 @@ import CaptureFab from '../capture/CaptureFab';
import stores from '../../AppStores';
import { CommonStyle } from '../styles/Common';
import GroupsBar from '../groups/GroupsBar';
import testID from '../common/helpers/testID';
import FeedList from '../common/components/FeedList';
import featuresService from '../common/services/features.service';
......@@ -109,7 +108,7 @@ export default class NewsfeedScreen extends Component {
if (newsfeed.filter == 'subscribed') {
return (
<View style={CommonStyle.flexContainer} {...testID('Newsfeed Screen')}>
<View style={CommonStyle.flexContainer} testID="NewsfeedScreen">
<FeedList
ref={newsfeed.setListRef}
feedStore={newsfeed.feedStore}
......@@ -122,7 +121,7 @@ export default class NewsfeedScreen extends Component {
}
return (
<View style={CommonStyle.flexContainer} {...testID('Newsfeed Screen')}>
<View style={CommonStyle.flexContainer} testID="NewsfeedScreen">
<NewsfeedList
newsfeed={newsfeed}
header={header}
......
const request = require('request')
const factory = function(data) {
return {
specDone: async (result) => {
if (result.status === 'failed') {
const reason = 'Suite: ' + result.description + ' was ' + result.status;
const url = `https://api-cloud.browserstack.com/app-automate/sessions/${data.sessiondID}.json`;
const options = {
json: true,
body: {status: 'failed', reason},
auth: {
'user': process.env.bsUSER,
'pass': process.env.bsKEY,
'sendImmediately': true
}
};
return await new Promise((resolve, reject) => {
try {
request.put(url, options, (err, res, body) => {
if (err) {
console.log('Error informing failed session to browserstack');
reject(err);
} else {
console.log('INFORMED');
resolve(body);
}
});
} catch (err) {
console.log('Error informing failed session to browserstack');
reject(err);
}
});
}
}
};
}
export default factory;
\ No newline at end of file
import wd from 'wd';
import path from 'path';
import { Version } from "../src/config/Version";
const settings = {
// install the current debug build
"androidLocal": {
"platformName": "Android",
"automationName": "UIAutomator2",
"deviceName": "Android Emulator",
"app": path.normalize(__dirname+'/../android/app/build/outputs/apk/debug/app-debug.apk')
},
// use the current installed binary (production/debug)
"androidLocalInstalled": {
"platformName": 'Android',
"deviceName": 'Android Emulator',
"appPackage": "com.minds.mobile",
"appActivity": "com.minds.mobile.MainActivity"
},
"iOSLocal": {
"platformName": 'iOS',
"platformVersion": '12.1',
"deviceName": 'iPhone X',
"automationName": 'XCUITest',
"app": path.normalize(__dirname+'/../Minds.app.zip'),
},
// run on browserstack devices
"browserStack": {
'browserstack.user' : process.env.bsUSER,
'browserstack.key' : process.env.bsKEY,
'browserstack.networkLogs': true,
'build' : Version.VERSION,
'name': 'single_test',
'app' : process.env.bsAPP,
'browserstack.debug' : true
}
}
const factory = function(name, overwrite) {
if (!settings[name]) {
throw new Error('Capability not defined');
}
const capabilities = settings[name];
if (overwrite) Object.assign(capabilities, overwrite);
let driver;
if (name === 'browserStack') {
driver = wd.promiseRemote("http://hub-cloud.browserstack.com/wd/hub");
} else {
driver = wd.promiseChainRemote('localhost', 4723);
}
return [driver, capabilities];
}
export default factory;
\ No newline at end of file
export const login = async(driver) => {
const username = await driver.elementByAccessibilityId('username input');
const password = await driver.elementByAccessibilityId('password input');
const loginButton = await driver.elementByAccessibilityId('login button');
await username.type(process.env.loginUser);
await password.type(process.env.loginPass);
await loginButton.click();
}
\ No newline at end of file
This diff is collapsed.