Merge branch 'AD-1519-visual-regression-cli' into AD-1396-log-404-page-views-when-previous-view-was-within-the-app
9
.gitignore
vendored
@ -16,10 +16,13 @@ webapp-dependencies.txt
|
||||
pids
|
||||
logs
|
||||
results
|
||||
|
||||
node_modules/*
|
||||
|
||||
build
|
||||
build/*
|
||||
|
||||
gemini-coverage/*
|
||||
gemini-report/*
|
||||
|
||||
node_modules/*
|
||||
|
||||
.DS_Store
|
||||
.env
|
||||
|
78
README.md
@ -1,18 +1,19 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
Onion is the web client for Ascribe. The idea is to have a well documented,
|
||||
easy to test, easy to hack, JavaScript application.
|
||||
Onion is the web client for Ascribe. The idea is to have a well documented, modern, easy to test, easy to hack, JavaScript application.
|
||||
|
||||
The code is JavaScript ECMA 6.
|
||||
The code is JavaScript 2015 / ECMAScript 6.
|
||||
|
||||
|
||||
Getting started
|
||||
===============
|
||||
|
||||
Install some nice extension for Chrom(e|ium):
|
||||
|
||||
- [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi)
|
||||
|
||||
- [Alt Developer Tools](https://github.com/goatslacker/alt-devtool)
|
||||
|
||||
```bash
|
||||
git clone git@github.com:ascribe/onion.git
|
||||
cd onion
|
||||
@ -37,17 +38,34 @@ Additionally, to work on the white labeling functionality, you need to edit your
|
||||
|
||||
JavaScript Code Conventions
|
||||
===========================
|
||||
|
||||
For this project, we're using:
|
||||
|
||||
* 4 Spaces
|
||||
* We use ES6
|
||||
* ES6
|
||||
* We don't use ES6's class declaration for React components because it does not support Mixins as well as Autobinding ([Blog post about it](http://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#autobinding))
|
||||
* We don't use camel case for file naming but in everything Javascript related
|
||||
* We use `let` instead of `var`: [SA Post](http://stackoverflow.com/questions/762011/javascript-let-keyword-vs-var-keyword)
|
||||
* We don't use Javascript's `Date` object, as its interface introduced bugs previously and we're including `momentjs` for other dependencies anyways
|
||||
* We use `momentjs` instead of Javascript's `Date` object, as the native `Date` interface previously introduced bugs and we're including `momentjs` for other dependencies anyway
|
||||
|
||||
Make sure to check out the [style guide](https://github.com/ascribe/javascript).
|
||||
|
||||
Linting
|
||||
-------
|
||||
|
||||
We use [ESLint](https://github.com/eslint/eslint) with our own [custom ruleset](.eslintrc).
|
||||
|
||||
|
||||
SCSS Code Conventions
|
||||
=====================
|
||||
|
||||
Install [lint-scss](https://github.com/brigade/scss-lint), check the [editor integration docs](https://github.com/brigade/scss-lint#editor-integration) to integrate the lint in your editor.
|
||||
|
||||
Some interesting links:
|
||||
* [Improving Sass code quality on theguardian.com](https://www.theguardian.com/info/developer-blog/2014/may/13/improving-sass-code-quality-on-theguardiancom)
|
||||
|
||||
|
||||
Branch names
|
||||
=====================
|
||||
============
|
||||
|
||||
To allow Github and JIRA to track branches while still allowing us to switch branches quickly using a ticket's number (and keep our peace of mind), we have the following rules for naming branches:
|
||||
|
||||
@ -61,22 +79,21 @@ AD-<JIRA-ticket-id>-brief-and-sane-description-of-the-ticket
|
||||
|
||||
where `brief-and-sane-description-of-the-ticket` does not need to equal to the issue or ticket's title.
|
||||
|
||||
|
||||
Example
|
||||
-------------
|
||||
-------
|
||||
|
||||
**JIRA ticket name:** `AD-1242 - Frontend caching for simple endpoints to measure perceived page load <more useless information>`
|
||||
|
||||
**Github branch name:** `AD-1242-caching-solution-for-stores`
|
||||
|
||||
SCSS Code Conventions
|
||||
=====================
|
||||
Install [lint-scss](https://github.com/brigade/scss-lint), check the [editor integration docs](https://github.com/brigade/scss-lint#editor-integration) to integrate the lint in your editor.
|
||||
|
||||
Some interesting links:
|
||||
* [Improving Sass code quality on theguardian.com](https://www.theguardian.com/info/developer-blog/2014/may/13/improving-sass-code-quality-on-theguardiancom)
|
||||
|
||||
|
||||
Testing
|
||||
===============
|
||||
=======
|
||||
|
||||
Unit Testing
|
||||
------------
|
||||
|
||||
We're using Facebook's jest to do testing as it integrates nicely with react.js as well.
|
||||
|
||||
Tests are always created per directory by creating a `__tests__` folder. To test a specific file, a `<file_name>_tests.js` file needs to be created.
|
||||
@ -86,7 +103,24 @@ This is due to the fact that jest's function mocking and ES6 module syntax are [
|
||||
|
||||
Therefore, to require a module in your test file, you need to use CommonJS's `require` syntax. Except for this, all tests can be written in ES6 syntax.
|
||||
|
||||
## Workflow
|
||||
Visual Regression Testing
|
||||
-------------------------
|
||||
|
||||
We're using [Gemini](https://github.com/gemini-testing/gemini) for visual regression tests because it supports both PhantomJS2 and SauceLabs.
|
||||
|
||||
See the [helper docs](test/gemini/README.md) for information on installing Gemini, its dependencies, and running and writing tests.
|
||||
|
||||
Integration Testing
|
||||
-------------------
|
||||
|
||||
We're using [Sauce Labs](https://saucelabs.com/home) with [WD.js](https://github.com/admc/wd) for integration testing across browser grids with Selenium.
|
||||
|
||||
See the [helper docs](test/integration/README.md) for information on each part of the test stack and how to run and write tests.
|
||||
|
||||
|
||||
Workflow
|
||||
========
|
||||
|
||||
Generally, when you're runing `gulp serve`, all tests are being run.
|
||||
If you want to test exclusively (without having the obnoxious ES6Linter warnings), you can just run `gulp jest:watch`.
|
||||
|
||||
@ -137,9 +171,16 @@ A: Easily by starting the your gulp process with the following command:
|
||||
ONION_BASE_URL='/' ONION_SERVER_URL='http://localhost.com:8000/' gulp serve
|
||||
```
|
||||
|
||||
Or, by adding these two your environment variables:
|
||||
```
|
||||
ONION_BASE_URL='/'
|
||||
ONION_SERVER_URL='http://localhost.com:8000/'
|
||||
```
|
||||
|
||||
Q: I want to know all dependencies that get bundled into the live build.
|
||||
A: ```browserify -e js/app.js --list > webapp-dependencies.txt```
|
||||
|
||||
|
||||
Reading list
|
||||
============
|
||||
|
||||
@ -152,7 +193,6 @@ Start here
|
||||
- [alt.js](http://alt.js.org/)
|
||||
- [alt.js readme](https://github.com/goatslacker/alt)
|
||||
|
||||
|
||||
Moar stuff
|
||||
----------
|
||||
|
||||
|
@ -97,7 +97,8 @@ gulp.task('browser-sync', function() {
|
||||
proxy: 'http://localhost:4000',
|
||||
port: 3000,
|
||||
open: false, // does not open the browser-window anymore (handled manually)
|
||||
ghostMode: false
|
||||
ghostMode: false,
|
||||
notify: false // stop showing the browsersync pop up
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -84,6 +84,7 @@ let PieceListToolbarFilterWidget = React.createClass({
|
||||
if (this.props.filterParams && this.props.filterParams.length) {
|
||||
return (
|
||||
<DropdownButton
|
||||
id="ascribe-piece-list-toolbar-filter-widget-dropdown"
|
||||
pullRight={true}
|
||||
title={filterIcon}
|
||||
className="ascribe-piece-list-toolbar-filter-widget">
|
||||
|
@ -45,7 +45,7 @@ let PieceListToolbarOrderWidget = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
let filterIcon = (
|
||||
let orderIcon = (
|
||||
<span>
|
||||
<span className="ascribe-icon icon-ascribe-sort" aria-hidden="true"></span>
|
||||
<span style={this.isOrderActive()}>·</span>
|
||||
@ -55,9 +55,10 @@ let PieceListToolbarOrderWidget = React.createClass({
|
||||
if (this.props.orderParams && this.props.orderParams.length) {
|
||||
return (
|
||||
<DropdownButton
|
||||
id="ascribe-piece-list-toolbar-order-widget-dropdown"
|
||||
pullRight={true}
|
||||
title={filterIcon}
|
||||
className="ascribe-piece-list-toolbar-filter-widget">
|
||||
className="ascribe-piece-list-toolbar-filter-widget"
|
||||
title={orderIcon}>
|
||||
<li style={{'textAlign': 'center'}}>
|
||||
<em>{getLangText('Sort by')}:</em>
|
||||
</li>
|
||||
|
@ -27,7 +27,7 @@ let CoaVerifyContainer = React.createClass({
|
||||
|
||||
return (
|
||||
<div className="ascribe-login-wrapper">
|
||||
<br/>
|
||||
<br />
|
||||
<div className="ascribe-login-text ascribe-login-header">
|
||||
{getLangText('Verify your Certificate of Authenticity')}
|
||||
</div>
|
||||
@ -37,7 +37,7 @@ let CoaVerifyContainer = React.createClass({
|
||||
signature={signature}/>
|
||||
<br />
|
||||
<br />
|
||||
{getLangText('ascribe is using the following public key for verification')}:
|
||||
{getLangText('ascribe is using the following public key for verification')}:
|
||||
<br />
|
||||
<pre>
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
@ -60,9 +60,8 @@ let CoaVerifyForm = React.createClass({
|
||||
},
|
||||
|
||||
handleSuccess(response){
|
||||
let notification = null;
|
||||
if (response.verdict) {
|
||||
notification = new GlobalNotificationModel(getLangText('Certificate of Authenticity successfully verified'), 'success');
|
||||
const notification = new GlobalNotificationModel(getLangText('Certificate of Authenticity successfully verified'), 'success');
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
}
|
||||
},
|
||||
@ -71,46 +70,44 @@ let CoaVerifyForm = React.createClass({
|
||||
const { message, signature } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form
|
||||
url={ApiUrls.coa_verify}
|
||||
handleSuccess={this.handleSuccess}
|
||||
buttons={
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-default btn-wide">
|
||||
{getLangText('Verify your Certificate of Authenticity')}
|
||||
</button>}
|
||||
spinner={
|
||||
<span className="btn btn-default btn-wide btn-spinner">
|
||||
<AscribeSpinner color="dark-blue" size="md" />
|
||||
</span>
|
||||
}>
|
||||
<Property
|
||||
name='message'
|
||||
label={getLangText('Message')}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={getLangText('Copy paste the message on the bottom of your Certificate of Authenticity')}
|
||||
autoComplete="on"
|
||||
defaultValue={message}
|
||||
name="username"
|
||||
required/>
|
||||
</Property>
|
||||
<Property
|
||||
name='signature'
|
||||
label="Signature"
|
||||
editable={true}
|
||||
overrideForm={true}>
|
||||
<InputTextAreaToggable
|
||||
rows={3}
|
||||
placeholder={getLangText('Copy paste the signature on the bottom of your Certificate of Authenticity')}
|
||||
defaultValue={signature}
|
||||
required/>
|
||||
</Property>
|
||||
<hr />
|
||||
</Form>
|
||||
</div>
|
||||
<Form
|
||||
url={ApiUrls.coa_verify}
|
||||
handleSuccess={this.handleSuccess}
|
||||
buttons={
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-default btn-wide">
|
||||
{getLangText('Verify your Certificate of Authenticity')}
|
||||
</button>
|
||||
}
|
||||
spinner={
|
||||
<span className="btn btn-default btn-wide btn-spinner">
|
||||
<AscribeSpinner color="dark-blue" size="md" />
|
||||
</span>
|
||||
}>
|
||||
<Property
|
||||
name='message'
|
||||
label={getLangText('Message')}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={getLangText('Copy paste the message on the bottom of your Certificate of Authenticity')}
|
||||
autoComplete="on"
|
||||
defaultValue={message}
|
||||
required />
|
||||
</Property>
|
||||
<Property
|
||||
name='signature'
|
||||
label="Signature"
|
||||
editable={true}
|
||||
overrideForm={true}>
|
||||
<InputTextAreaToggable
|
||||
rows={3}
|
||||
placeholder={getLangText('Copy paste the signature on the bottom of your Certificate of Authenticity')}
|
||||
defaultValue={signature}
|
||||
required />
|
||||
</Property>
|
||||
<hr />
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -173,6 +173,7 @@ let Header = React.createClass({
|
||||
account = (
|
||||
<DropdownButton
|
||||
ref='dropdownbutton'
|
||||
id="nav-route-user-dropdown"
|
||||
eventKey="1"
|
||||
title={currentUser.username}>
|
||||
<LinkContainer
|
||||
|
@ -126,6 +126,7 @@ let HeaderNotifications = React.createClass({
|
||||
<Nav navbar right>
|
||||
<DropdownButton
|
||||
ref='dropdownbutton'
|
||||
id="header-notification-dropdown"
|
||||
eventKey="1"
|
||||
title={
|
||||
<span>
|
||||
|
@ -30,6 +30,7 @@ let NavRoutesLinksLink = React.createClass({
|
||||
return (
|
||||
<DropdownButton
|
||||
disabled={disabled}
|
||||
id={`nav-route-${headerTitle.toLowerCase()}-dropdown`}
|
||||
title={headerTitle}>
|
||||
{children}
|
||||
</DropdownButton>
|
||||
|
@ -78,7 +78,7 @@ let ROUTES = {
|
||||
headerTitle='COLLECTION'
|
||||
disableOn='noPieces' />
|
||||
<Route path='editions/:editionId' component={EditionContainer} />
|
||||
<Route path='verify' component={CoaVerifyContainer} />
|
||||
<Route path='coa_verify' component={CoaVerifyContainer} />
|
||||
<Route path='pieces/:pieceId' component={CylandPieceContainer} />
|
||||
<Route path='*' component={ErrorNotFoundPage} />
|
||||
</Route>
|
||||
@ -114,7 +114,7 @@ let ROUTES = {
|
||||
disableOn='noPieces' />
|
||||
<Route path='pieces/:pieceId' component={PieceContainer} />
|
||||
<Route path='editions/:editionId' component={EditionContainer} />
|
||||
<Route path='verify' component={CoaVerifyContainer} />
|
||||
<Route path='coa_verify' component={CoaVerifyContainer} />
|
||||
<Route path='*' component={ErrorNotFoundPage} />
|
||||
</Route>
|
||||
),
|
||||
@ -159,7 +159,7 @@ let ROUTES = {
|
||||
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(IkonotvContractNotifications)} />
|
||||
<Route path='pieces/:pieceId' component={IkonotvPieceContainer} />
|
||||
<Route path='editions/:editionId' component={EditionContainer} />
|
||||
<Route path='verify' component={CoaVerifyContainer} />
|
||||
<Route path='coa_verify' component={CoaVerifyContainer} />
|
||||
<Route path='*' component={ErrorNotFoundPage} />
|
||||
</Route>
|
||||
),
|
||||
@ -196,7 +196,7 @@ let ROUTES = {
|
||||
disableOn='noPieces' />
|
||||
<Route path='pieces/:pieceId' component={MarketPieceContainer} />
|
||||
<Route path='editions/:editionId' component={MarketEditionContainer} />
|
||||
<Route path='verify' component={CoaVerifyContainer} />
|
||||
<Route path='coa_verify' component={CoaVerifyContainer} />
|
||||
<Route path='*' component={ErrorNotFoundPage} />
|
||||
</Route>
|
||||
),
|
||||
@ -233,7 +233,7 @@ let ROUTES = {
|
||||
disableOn='noPieces' />
|
||||
<Route path='pieces/:pieceId' component={MarketPieceContainer} />
|
||||
<Route path='editions/:editionId' component={MarketEditionContainer} />
|
||||
<Route path='verify' component={CoaVerifyContainer} />
|
||||
<Route path='coa_verify' component={CoaVerifyContainer} />
|
||||
<Route path='*' component={ErrorNotFoundPage} />
|
||||
</Route>
|
||||
)
|
||||
|
19
package.json
@ -12,8 +12,21 @@
|
||||
"postinstall": "npm run build",
|
||||
"build": "gulp build --production",
|
||||
"start": "node server.js",
|
||||
"test": "mocha",
|
||||
"tunnel": "node test/tunnel.js"
|
||||
"test": "npm run sauce-test",
|
||||
"sauce-test": "mocha ./test/integration/tests/",
|
||||
"tunnel": "node ./test/integration/tunnel.js",
|
||||
"vi-clean": "rm -rf ./gemini-report",
|
||||
"vi-phantom": "phantomjs --webdriver=4444",
|
||||
"vi-update": "gemini update -c ./test/gemini/.gemini.yml",
|
||||
"vi-test": "npm run vi-clean && gemini test -c ./test/gemini/.gemini.yml --reporter html --reporter vflat || true",
|
||||
"vi-test:all": "npm run vi-test",
|
||||
"vi-test:main": "npm run vi-test -- --browser MainDesktop --browser MainMobile",
|
||||
"vi-test:whitelabel": "GEMINI_BROWSERS='CcDesktop, CcMobile, CylandDesktop, CylandMobile, IkonotvDesktop, IkonotvMobile, LumenusDesktop, LumenusMobile, 23viviDesktop, 23viviMobile' npm run vi-test",
|
||||
"vi-test:cc": "npm run vi-test -- --browser CcDesktop --browser CcMobile",
|
||||
"vi-test:cyland": "npm run vi-test -- --browser CylandDesktop --browser CylandMobile",
|
||||
"vi-test:ikonotv": "npm run vi-test -- --browser IkonotvDesktop --browser IkonotvMobile",
|
||||
"vi-test:lumenus": "npm run vi-test -- --browser LumenusDesktop --browser LumenusMobile",
|
||||
"vi-test:23vivi": "npm run vi-test -- --browser 23viviDesktop --browser 23viviMobile"
|
||||
},
|
||||
"browser": {
|
||||
"fineUploader": "./js/components/ascribe_uploader/vendor/s3.fine-uploader.js"
|
||||
@ -42,8 +55,10 @@
|
||||
"chai-as-promised": "^5.1.0",
|
||||
"colors": "^1.1.2",
|
||||
"dotenv": "^1.2.0",
|
||||
"gemini": "^2.1.0",
|
||||
"jest-cli": "^0.4.0",
|
||||
"mocha": "^2.3.4",
|
||||
"phantomjs2": "^2.0.2",
|
||||
"sauce-connect-launcher": "^0.13.0",
|
||||
"wd": "^0.4.0"
|
||||
},
|
||||
|
62
phantomjs/launch_app_and_login.js
Normal file
@ -0,0 +1,62 @@
|
||||
'use strict';
|
||||
|
||||
var liveEnv = 'https://www.ascribe.io/app/login';
|
||||
// Note that if you are trying to access staging, you will need to use
|
||||
// the --ignore-ssl-errors=true flag on phantomjs
|
||||
var stagingEnv = 'https://www.ascribe.ninja/app/login';
|
||||
var localEnv = 'http://localhost.com:3000/login';
|
||||
|
||||
var page = require('webpage').create();
|
||||
page.open(localEnv, function(status) {
|
||||
var attemptedToLogIn;
|
||||
var loginCheckInterval;
|
||||
|
||||
console.log('Status: ' + status);
|
||||
|
||||
if (status === 'success') {
|
||||
console.log('Attempting to log in...');
|
||||
|
||||
attemptedToLogIn = page.evaluate(function () {
|
||||
try {
|
||||
var inputForm = document.querySelector('.ascribe-login-wrapper');
|
||||
var email = inputForm.querySelector('input[type=email]');
|
||||
var password = inputForm.querySelector('input[type=password]');
|
||||
var submitBtn = inputForm.querySelector('button[type=submit]');
|
||||
|
||||
email.value = 'dimi@mailinator.com';
|
||||
password.value = '0000000000';
|
||||
submitBtn.click();
|
||||
|
||||
return true;
|
||||
} catch (ex) {
|
||||
console.log('Error while trying to find login elements, not logging in.');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (attemptedToLogIn) {
|
||||
loginCheckInterval = setInterval(function () {
|
||||
var loggedIn = page.evaluate(function () {
|
||||
// When they log in, they are taken to the collections page.
|
||||
// When the piece list is loaded, the accordion list is either available or
|
||||
// shows a placeholder, so let's check for these elements to determine
|
||||
// when login is finished
|
||||
return !!(document.querySelector('.ascribe-accordion-list:not(.ascribe-loading-position)') ||
|
||||
document.querySelector('.ascribe-accordion-list-placeholder'));
|
||||
});
|
||||
|
||||
if (loggedIn) {
|
||||
clearInterval(loginCheckInterval);
|
||||
console.log('Successfully logged in.');
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
console.log('Something happened while trying to log in, aborting...');
|
||||
phantom.exit();
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log('Failed to load page, exiing...');
|
||||
phantom.exit();
|
||||
}
|
||||
});
|
133
test/gemini/.gemini.yml
Normal file
@ -0,0 +1,133 @@
|
||||
rootUrl: http://localhost.com:3000/
|
||||
sessionsPerBrowser: 1
|
||||
|
||||
browsers:
|
||||
MainDesktop:
|
||||
rootUrl: http://localhost.com:3000/
|
||||
screenshotsDir: './screenshots/main-desktop'
|
||||
windowSize: 1900x1080
|
||||
desiredCapabilities:
|
||||
browserName: phantomjs
|
||||
|
||||
MainMobile:
|
||||
rootUrl: http://localhost.com:3000/
|
||||
screenshotsDir: './screenshots/main-mobile'
|
||||
windowSize: 600x1056
|
||||
desiredCapabilities:
|
||||
browserName: phantomjs
|
||||
|
||||
CcDesktop:
|
||||
rootUrl: http://cc.localhost.com:3000/
|
||||
screenshotsDir: './screenshots/cc-desktop'
|
||||
windowSize: 1900x1080
|
||||
desiredCapabilities:
|
||||
browserName: phantomjs
|
||||
|
||||
CcMobile:
|
||||
rootUrl: http://cc.localhost.com:3000/
|
||||
screenshotsDir: './screenshots/cc-mobile'
|
||||
windowSize: 600x1056
|
||||
desiredCapabilities:
|
||||
browserName: phantomjs
|
||||
|
||||
CylandDesktop:
|
||||
rootUrl: http://cyland.localhost.com:3000/
|
||||
screenshotsDir: './screenshots/cyland-desktop'
|
||||
windowSize: 1900x1080
|
||||
desiredCapabilities:
|
||||
browserName: phantomjs
|
||||
|
||||
CylandMobile:
|
||||
rootUrl: http://cyland.localhost.com:3000/
|
||||
screenshotsDir: './screenshots/cyland-mobile'
|
||||
windowSize: 600x1056
|
||||
desiredCapabilities:
|
||||
browserName: phantomjs
|
||||
|
||||
IkonotvDesktop:
|
||||
rootUrl: http://ikonotv.localhost.com:3000/
|
||||
screenshotsDir: './screenshots/ikonotv-desktop'
|
||||
windowSize: 1900x1080
|
||||
desiredCapabilities:
|
||||
browserName: phantomjs
|
||||
|
||||
IkonotvMobile:
|
||||
rootUrl: http://ikonotv.localhost.com:3000/
|
||||
screenshotsDir: './screenshots/ikonotv-mobile'
|
||||
windowSize: 600x1056
|
||||
desiredCapabilities:
|
||||
browserName: phantomjs
|
||||
|
||||
LumenusDesktop:
|
||||
rootUrl: http://lumenus.localhost.com:3000/
|
||||
screenshotsDir: './screenshots/lumenus-desktop'
|
||||
windowSize: 1900x1080
|
||||
desiredCapabilities:
|
||||
browserName: phantomjs
|
||||
|
||||
LumenusMobile:
|
||||
rootUrl: http://lumenus.localhost.com:3000/
|
||||
screenshotsDir: './screenshots/lumenus-mobile'
|
||||
windowSize: 600x1056
|
||||
desiredCapabilities:
|
||||
browserName: phantomjs
|
||||
|
||||
23viviDesktop:
|
||||
rootUrl: http://23vivi.localhost.com:3000/
|
||||
screenshotsDir: './screenshots/23vivi-desktop'
|
||||
windowSize: 1900x1080
|
||||
desiredCapabilities:
|
||||
browserName: phantomjs
|
||||
|
||||
23viviMobile:
|
||||
rootUrl: http://23vivi.localhost.com:3000/
|
||||
screenshotsDir: './screenshots/23vivi-mobile'
|
||||
windowSize: 600x1056
|
||||
desiredCapabilities:
|
||||
browserName: phantomjs
|
||||
|
||||
sets:
|
||||
main:
|
||||
files:
|
||||
- tests/main
|
||||
browsers:
|
||||
- MainDesktop
|
||||
- MainMobile
|
||||
cc:
|
||||
files:
|
||||
- tests/whitelabel/shared
|
||||
browsers:
|
||||
- CcDesktop
|
||||
- CcMobile
|
||||
|
||||
cyland:
|
||||
files:
|
||||
- tests/whitelabel/shared
|
||||
- tests/whitelabel/cyland
|
||||
browsers:
|
||||
- CylandDesktop
|
||||
- CylandMobile
|
||||
|
||||
ikonotv:
|
||||
files:
|
||||
- tests/whitelabel/shared
|
||||
- tests/whitelabel/ikonotv
|
||||
browsers:
|
||||
- IkonotvDesktop
|
||||
- IkonotvMobile
|
||||
|
||||
lumenus:
|
||||
files:
|
||||
- tests/whitelabel/shared
|
||||
- tests/whitelabel/lumenus
|
||||
browsers:
|
||||
- LumenusDesktop
|
||||
- LumenusMobile
|
||||
|
||||
23vivi:
|
||||
files:
|
||||
- tests/whitelabel/shared
|
||||
- tests/whitelabel/23vivi
|
||||
browsers:
|
||||
- 23viviDesktop
|
||||
- 23viviMobile
|
205
test/gemini/README.md
Normal file
@ -0,0 +1,205 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
When in doubt, see [Gemini](https://github.com/gemini-testing/gemini) and [their
|
||||
docs](https://github.com/gemini-testing/gemini/tree/master/doc) for more information as well as configuration options.
|
||||
|
||||
Contents
|
||||
========
|
||||
|
||||
1. [Installation](#installation)
|
||||
1. [Running Tests](#running-tests)
|
||||
1. [Gemini Usage and Writing Tests](#gemini-usage-and-writing-tests)
|
||||
1. [PhantomJS](#phantomjs)
|
||||
1. [TODO](#todo)
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
First make sure that you're using NodeJS 5.0+ as the tests are written using ES6 syntax.
|
||||
|
||||
Then, install [PhantomJS2](https://www.npmjs.com/package/phantomjs2):
|
||||
|
||||
```bash
|
||||
# Until phantomjs2 is updated for the new 2.1 version of PhantomJS, use the following (go to https://bitbucket.org/ariya/phantomjs/downloads to find a build for your OS)
|
||||
npm install -g phantomjs2 --phantomjs_downloadurl=https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-macosx.zip
|
||||
npm install --save-dev phantomjs2 --phantomjs_downloadurl=https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-macosx.zip
|
||||
|
||||
# If using OSX, you may have to install upx and decompress the binary downloaded by npm manually:
|
||||
brew install upx
|
||||
|
||||
# Navigate to the binary, ie. /Users/Brett/.nvm/versions/node/v5.4.0/lib/node_modules/phantomjs2/lib/phantom/bin/phantomjs
|
||||
upx -d phantomjs
|
||||
|
||||
```
|
||||
|
||||
Finally, [install Gemini globally and locally with npm](https://github.com/gemini-testing/gemini/blob/master/README.md#installation).
|
||||
|
||||
|
||||
Running Tests
|
||||
=============
|
||||
|
||||
Run PhantomJS:
|
||||
|
||||
```bash
|
||||
npm run vi-phantom
|
||||
```
|
||||
|
||||
And then run Gemini tests:
|
||||
|
||||
```bash
|
||||
npm run vi-test
|
||||
|
||||
# Run only main tests
|
||||
npm run vi-test:main
|
||||
|
||||
# Run only whitelabel tests
|
||||
npm run vi-test:whitelabel
|
||||
|
||||
# Run only specific whitelabel tests
|
||||
npm run vi-test:cyland
|
||||
```
|
||||
|
||||
If you've made changes and want them to be the new baseline (ie. it's a correct change--**make sure** to test there are
|
||||
no regressions first!), use
|
||||
|
||||
```bash
|
||||
npm run vi-update
|
||||
|
||||
# Update just the main app for desktop and mobile
|
||||
npm run vi-update -- --browser MainDesktop --browser MainMobile
|
||||
```
|
||||
|
||||
|
||||
Gemini Usage and Writing Tests
|
||||
==============================
|
||||
|
||||
While Gemini itself is easy to use on simple, static pages, there are some nice to knows when dealing with a single page
|
||||
app like ours (where much of it is behind an authentication barrier as well).
|
||||
|
||||
Command Line Interface
|
||||
----------------------
|
||||
|
||||
See [the docs](https://github.com/gemini-testing/gemini/blob/master/doc/commands.md) on the commands that are available.
|
||||
`npm run vi-*` is set up with some of these commands, but you may want to build your own or learn about some of the
|
||||
other functions.
|
||||
|
||||
Authentication
|
||||
--------------
|
||||
|
||||
Authentication presents a tricky problem with Gemini, since we can't inject any cookies or even run a start up script
|
||||
through the browser before letting Gemini hook in. The solution is to script the log in process through Gemini, and put
|
||||
waits for the log in to succeed, before testing parts of the app that require the authentication.
|
||||
|
||||
Browser Session States
|
||||
----------------------
|
||||
|
||||
Gemini will start a new instance of the browser for each browser configuration defined in the .gemini.yml file when
|
||||
Gemini's launched (ie. `gemini update`, `gemini test`, etc).
|
||||
|
||||
Although each new suite will cause the testing browser to be refreshed, the above means that cookies and other
|
||||
persistent state will be kept across suites for a browser across all runs, even if the suites are from different files.
|
||||
|
||||
**What this comes down to is**: once you've logged in, you'll stay logged in until you decide to log out or the running
|
||||
instance of Gemini ends. In general practice, it's a good idea to clear the state of the app at the end of each suite of
|
||||
tests by logging out.
|
||||
|
||||
(**Note**: Persistent storage, such as local storage, has not been explicitly tested as to whether they are kept, but as
|
||||
the cookies are cleared each time, this seems unlikely)
|
||||
|
||||
Test Reporting
|
||||
--------------
|
||||
|
||||
Using the `--reporter html` flag with Gemini will produce a webpage with the test's results in `onion/gemini-report`
|
||||
that will show the old, new, and diff images. Using this is highly recommended (and fun!) and is used by default in `npm
|
||||
run vi-test`.
|
||||
|
||||
Writing Tests
|
||||
-------------
|
||||
|
||||
See [the docs](https://github.com/gemini-testing/gemini/blob/master/doc/tests.md), and the [section on the available
|
||||
actions](https://github.com/gemini-testing/gemini/blob/master/doc/tests.md#available-actions) for what scripted actions
|
||||
are available.
|
||||
|
||||
Our tests are located in `onion/test/gemini/tests/`.
|
||||
|
||||
**It would be nice if we kept the whitelabels up to date.**
|
||||
|
||||
Some useful tips:
|
||||
* The `find()` method in the callbacks is equivalent to `document.querySelector`; it will only return the first
|
||||
element found that matches the selector. Use pseudo classes like `nth-of-type()`, `nth-child()`, and etc. to select
|
||||
later elements.
|
||||
* Nested suites inherit from their parent suites' configurations, but will **override** their inherited configuration
|
||||
if another is specified. For example, if `parentSuite` had a `.before()` method, all children of `parentSuite` would
|
||||
run its `.before()`, but if any of the children specified their own `.before()`, those children would **not** run
|
||||
`parentSuite`'s `.before()`.
|
||||
* Gemini takes a screenshot of the minimum bounding rect for all specified selectors, so this means you can't take a
|
||||
screenshot of two items far away from each other without the rest being considered (ie. trying to get the header and
|
||||
footer)
|
||||
* Unfortunately, `setCaptureElements` and `ignoreElements` will only apply for the first element found matching those
|
||||
selectors.
|
||||
|
||||
PhantomJS
|
||||
=========
|
||||
|
||||
[PhantomJS](http://phantomjs.org/) is a headless browser that allows us to run tests and take screenshots without
|
||||
needing a browser.
|
||||
|
||||
Its second version (PhantomJS2) uses a much more recent version of Webkit, and is a big reason why Gemini (as opposed to
|
||||
other utilities, ie. PhantomCSS) was chosen. Due to the large number of breaking changes introduced between PhantomJS
|
||||
1.9 to 2.0, a large number of tools (ie. CasperJS) are, at the time of writing, lacking support for 2.0.
|
||||
|
||||
While you don't need to know too much about PhantomJS to use and write Gemini tests, there are still a number of useful
|
||||
things to know about.
|
||||
|
||||
Useful features
|
||||
---------------
|
||||
|
||||
You can find the full list of CLI commands in the [documentation](http://phantomjs.org/api/command-line.html).
|
||||
|
||||
Flags that are of particular interest to us:
|
||||
* `--webdriver=4444`: sets the webdriver port to be 4444, the default webdriver port that Gemini expects.
|
||||
* `--ignore-ssl-errors=true`: ignores any SSL errors that may occur. Particular useful when hooking up the tests to
|
||||
staging, as the certificate we use is self-signed.
|
||||
* `--ssl-protocol=any`: allows any ssl protocol to be used. May be useful when `--ignore-ssl-errors=true` doesn't work.
|
||||
* '--remote-debugger-port`: allows for remote debugging the running PhantomJS instance. More on this later.
|
||||
|
||||
Troubleshooting and Debugging
|
||||
-----------------------------
|
||||
|
||||
Remote debugging is possible with PhantomJS using the `--remote-debugger-port` option. See the [troubleshooting
|
||||
docs](http://phantomjs.org/troubleshooting.html).
|
||||
|
||||
To begin using it, add `debugger;` statements to the file being run by `phantomjs`, and access the port number specified
|
||||
after `--remote-debugger-port` on localhost:
|
||||
|
||||
```bash
|
||||
phantomjs --remote-debugger-port=9000 debug.js
|
||||
```
|
||||
|
||||
PhantomJS will start and then immediately breakpoint. Go to http://localhost:9000/webkit/inspector/inspector.html?page=1
|
||||
and then to its console tab. Go to your first breakpoint (the first `debugger;` statement executed) by running `__run()`
|
||||
in the console tab. Subsequent breakpoints can be reached by successively running `__run()` in that same console tab.
|
||||
|
||||
At each breakpoint, you can to http://localhost:9000 on a new browser tab and click on one of the links to go to the
|
||||
current execution state of that breakpoint on the page you're on.
|
||||
|
||||
---
|
||||
|
||||
To simplify triaging simple issues and test if everything is working, I've added a short test script that can be run
|
||||
with PhantomJS to check if it can access the web app and log in. You can edit the `lauch_app_and_login.js` file to
|
||||
change the environment to run against.
|
||||
|
||||
```bash
|
||||
# In root /onion folder
|
||||
phantomjs phantomjs/launch_app_and_login.js
|
||||
```
|
||||
|
||||
|
||||
TODO
|
||||
====
|
||||
|
||||
* Write scripts to automate creation of test users (and modify tests to accomodate)
|
||||
* Set scripts with rootUrls pointing to staging / live using environment variables
|
||||
* Set up with Sauce Labs
|
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |