mirror of
https://github.com/ascribe/onion.git
synced 2024-12-22 17:33:14 +01:00
Merge pull request #146 from ascribe/AD-1519-visual-regression-cli
AD-1519 Visual regression tests
This commit is contained in:
commit
3c6dea3585
8
.gitignore
vendored
8
.gitignore
vendored
@ -17,9 +17,13 @@ pids
|
|||||||
logs
|
logs
|
||||||
results
|
results
|
||||||
|
|
||||||
node_modules/*
|
build/*
|
||||||
|
|
||||||
build
|
gemini-coverage/*
|
||||||
|
gemini-report/*
|
||||||
|
test/gemini/screenshots/*
|
||||||
|
|
||||||
|
node_modules/*
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env
|
.env
|
||||||
|
76
README.md
76
README.md
@ -1,17 +1,18 @@
|
|||||||
Introduction
|
Introduction
|
||||||
============
|
============
|
||||||
|
|
||||||
Onion is the web client for Ascribe. The idea is to have a well documented,
|
Onion is the web client for Ascribe. The idea is to have a well documented, modern, easy to test, easy to hack, JavaScript application.
|
||||||
easy to test, easy to hack, JavaScript application.
|
|
||||||
|
|
||||||
The code is JavaScript ECMA 6.
|
The code is JavaScript 2015 / ECMAScript 6.
|
||||||
|
|
||||||
|
|
||||||
Getting started
|
Getting started
|
||||||
===============
|
===============
|
||||||
|
|
||||||
Install some nice extension for Chrom(e|ium):
|
Install some nice extension for Chrom(e|ium):
|
||||||
|
|
||||||
- [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi)
|
- [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi)
|
||||||
|
- [Alt Developer Tools](https://github.com/goatslacker/alt-devtool)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone git@github.com:ascribe/onion.git
|
git clone git@github.com:ascribe/onion.git
|
||||||
@ -37,17 +38,34 @@ Additionally, to work on the white labeling functionality, you need to edit your
|
|||||||
|
|
||||||
JavaScript Code Conventions
|
JavaScript Code Conventions
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
For this project, we're using:
|
For this project, we're using:
|
||||||
|
|
||||||
* 4 Spaces
|
* 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 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 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 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
|
||||||
* We don't use Javascript's `Date` object, as its interface introduced bugs previously and we're including `momentjs` for other dependencies anyways
|
|
||||||
|
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
|
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:
|
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.
|
where `brief-and-sane-description-of-the-ticket` does not need to equal to the issue or ticket's title.
|
||||||
|
|
||||||
|
|
||||||
Example
|
Example
|
||||||
-------------
|
-------
|
||||||
|
|
||||||
**JIRA ticket name:** `AD-1242 - Frontend caching for simple endpoints to measure perceived page load <more useless information>`
|
**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`
|
**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
|
Testing
|
||||||
===============
|
=======
|
||||||
|
|
||||||
|
Unit Testing
|
||||||
|
------------
|
||||||
|
|
||||||
We're using Facebook's jest to do testing as it integrates nicely with react.js as well.
|
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.
|
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.
|
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.
|
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`.
|
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
|
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.
|
Q: I want to know all dependencies that get bundled into the live build.
|
||||||
A: ```browserify -e js/app.js --list > webapp-dependencies.txt```
|
A: ```browserify -e js/app.js --list > webapp-dependencies.txt```
|
||||||
|
|
||||||
|
|
||||||
Reading list
|
Reading list
|
||||||
============
|
============
|
||||||
|
|
||||||
@ -152,7 +193,6 @@ Start here
|
|||||||
- [alt.js](http://alt.js.org/)
|
- [alt.js](http://alt.js.org/)
|
||||||
- [alt.js readme](https://github.com/goatslacker/alt)
|
- [alt.js readme](https://github.com/goatslacker/alt)
|
||||||
|
|
||||||
|
|
||||||
Moar stuff
|
Moar stuff
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
@ -97,7 +97,8 @@ gulp.task('browser-sync', function() {
|
|||||||
proxy: 'http://localhost:4000',
|
proxy: 'http://localhost:4000',
|
||||||
port: 3000,
|
port: 3000,
|
||||||
open: false, // does not open the browser-window anymore (handled manually)
|
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) {
|
if (this.props.filterParams && this.props.filterParams.length) {
|
||||||
return (
|
return (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
|
id="ascribe-piece-list-toolbar-filter-widget-dropdown"
|
||||||
pullRight={true}
|
pullRight={true}
|
||||||
title={filterIcon}
|
title={filterIcon}
|
||||||
className="ascribe-piece-list-toolbar-filter-widget">
|
className="ascribe-piece-list-toolbar-filter-widget">
|
||||||
|
@ -45,7 +45,7 @@ let PieceListToolbarOrderWidget = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let filterIcon = (
|
let orderIcon = (
|
||||||
<span>
|
<span>
|
||||||
<span className="ascribe-icon icon-ascribe-sort" aria-hidden="true"></span>
|
<span className="ascribe-icon icon-ascribe-sort" aria-hidden="true"></span>
|
||||||
<span style={this.isOrderActive()}>·</span>
|
<span style={this.isOrderActive()}>·</span>
|
||||||
@ -55,9 +55,10 @@ let PieceListToolbarOrderWidget = React.createClass({
|
|||||||
if (this.props.orderParams && this.props.orderParams.length) {
|
if (this.props.orderParams && this.props.orderParams.length) {
|
||||||
return (
|
return (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
|
id="ascribe-piece-list-toolbar-order-widget-dropdown"
|
||||||
pullRight={true}
|
pullRight={true}
|
||||||
title={filterIcon}
|
className="ascribe-piece-list-toolbar-filter-widget"
|
||||||
className="ascribe-piece-list-toolbar-filter-widget">
|
title={orderIcon}>
|
||||||
<li style={{'textAlign': 'center'}}>
|
<li style={{'textAlign': 'center'}}>
|
||||||
<em>{getLangText('Sort by')}:</em>
|
<em>{getLangText('Sort by')}:</em>
|
||||||
</li>
|
</li>
|
||||||
|
@ -27,7 +27,7 @@ let CoaVerifyContainer = React.createClass({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ascribe-login-wrapper">
|
<div className="ascribe-login-wrapper">
|
||||||
<br/>
|
<br />
|
||||||
<div className="ascribe-login-text ascribe-login-header">
|
<div className="ascribe-login-text ascribe-login-header">
|
||||||
{getLangText('Verify your Certificate of Authenticity')}
|
{getLangText('Verify your Certificate of Authenticity')}
|
||||||
</div>
|
</div>
|
||||||
@ -37,7 +37,7 @@ let CoaVerifyContainer = React.createClass({
|
|||||||
signature={signature}/>
|
signature={signature}/>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
{getLangText('ascribe is using the following public key for verification')}:
|
{getLangText('ascribe is using the following public key for verification')}:
|
||||||
<br />
|
<br />
|
||||||
<pre>
|
<pre>
|
||||||
-----BEGIN PUBLIC KEY-----
|
-----BEGIN PUBLIC KEY-----
|
||||||
@ -60,9 +60,8 @@ let CoaVerifyForm = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleSuccess(response){
|
handleSuccess(response){
|
||||||
let notification = null;
|
|
||||||
if (response.verdict) {
|
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);
|
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -71,46 +70,44 @@ let CoaVerifyForm = React.createClass({
|
|||||||
const { message, signature } = this.props;
|
const { message, signature } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Form
|
||||||
<Form
|
url={ApiUrls.coa_verify}
|
||||||
url={ApiUrls.coa_verify}
|
handleSuccess={this.handleSuccess}
|
||||||
handleSuccess={this.handleSuccess}
|
buttons={
|
||||||
buttons={
|
<button
|
||||||
<button
|
type="submit"
|
||||||
type="submit"
|
className="btn btn-default btn-wide">
|
||||||
className="btn btn-default btn-wide">
|
{getLangText('Verify your Certificate of Authenticity')}
|
||||||
{getLangText('Verify your Certificate of Authenticity')}
|
</button>
|
||||||
</button>}
|
}
|
||||||
spinner={
|
spinner={
|
||||||
<span className="btn btn-default btn-wide btn-spinner">
|
<span className="btn btn-default btn-wide btn-spinner">
|
||||||
<AscribeSpinner color="dark-blue" size="md" />
|
<AscribeSpinner color="dark-blue" size="md" />
|
||||||
</span>
|
</span>
|
||||||
}>
|
}>
|
||||||
<Property
|
<Property
|
||||||
name='message'
|
name='message'
|
||||||
label={getLangText('Message')}>
|
label={getLangText('Message')}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={getLangText('Copy paste the message on the bottom of your Certificate of Authenticity')}
|
placeholder={getLangText('Copy paste the message on the bottom of your Certificate of Authenticity')}
|
||||||
autoComplete="on"
|
autoComplete="on"
|
||||||
defaultValue={message}
|
defaultValue={message}
|
||||||
name="username"
|
required />
|
||||||
required/>
|
</Property>
|
||||||
</Property>
|
<Property
|
||||||
<Property
|
name='signature'
|
||||||
name='signature'
|
label="Signature"
|
||||||
label="Signature"
|
editable={true}
|
||||||
editable={true}
|
overrideForm={true}>
|
||||||
overrideForm={true}>
|
<InputTextAreaToggable
|
||||||
<InputTextAreaToggable
|
rows={3}
|
||||||
rows={3}
|
placeholder={getLangText('Copy paste the signature on the bottom of your Certificate of Authenticity')}
|
||||||
placeholder={getLangText('Copy paste the signature on the bottom of your Certificate of Authenticity')}
|
defaultValue={signature}
|
||||||
defaultValue={signature}
|
required />
|
||||||
required/>
|
</Property>
|
||||||
</Property>
|
<hr />
|
||||||
<hr />
|
</Form>
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -173,6 +173,7 @@ let Header = React.createClass({
|
|||||||
account = (
|
account = (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
ref='dropdownbutton'
|
ref='dropdownbutton'
|
||||||
|
id="nav-route-user-dropdown"
|
||||||
eventKey="1"
|
eventKey="1"
|
||||||
title={currentUser.username}>
|
title={currentUser.username}>
|
||||||
<LinkContainer
|
<LinkContainer
|
||||||
|
@ -126,6 +126,7 @@ let HeaderNotifications = React.createClass({
|
|||||||
<Nav navbar right>
|
<Nav navbar right>
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
ref='dropdownbutton'
|
ref='dropdownbutton'
|
||||||
|
id="header-notification-dropdown"
|
||||||
eventKey="1"
|
eventKey="1"
|
||||||
title={
|
title={
|
||||||
<span>
|
<span>
|
||||||
|
@ -30,6 +30,7 @@ let NavRoutesLinksLink = React.createClass({
|
|||||||
return (
|
return (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
id={`nav-route-${headerTitle.toLowerCase()}-dropdown`}
|
||||||
title={headerTitle}>
|
title={headerTitle}>
|
||||||
{children}
|
{children}
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
|
@ -57,7 +57,7 @@ const ROUTES = {
|
|||||||
headerTitle='COLLECTION'/>
|
headerTitle='COLLECTION'/>
|
||||||
<Route path='pieces/:pieceId' component={SluicePieceContainer} />
|
<Route path='pieces/:pieceId' component={SluicePieceContainer} />
|
||||||
<Route path='editions/:editionId' component={EditionContainer} />
|
<Route path='editions/:editionId' component={EditionContainer} />
|
||||||
<Route path='verify' component={CoaVerifyContainer} />
|
<Route path='coa_verify' component={CoaVerifyContainer} />
|
||||||
<Route path='*' component={ErrorNotFoundPage} />
|
<Route path='*' component={ErrorNotFoundPage} />
|
||||||
</Route>
|
</Route>
|
||||||
),
|
),
|
||||||
@ -97,7 +97,7 @@ const ROUTES = {
|
|||||||
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SPSettingsContainer)}/>
|
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SPSettingsContainer)}/>
|
||||||
<Route path='pieces/:pieceId' component={SPPieceContainer} />
|
<Route path='pieces/:pieceId' component={SPPieceContainer} />
|
||||||
<Route path='editions/:editionId' component={EditionContainer} />
|
<Route path='editions/:editionId' component={EditionContainer} />
|
||||||
<Route path='verify' component={CoaVerifyContainer} />
|
<Route path='coa_verify' component={CoaVerifyContainer} />
|
||||||
<Route path='*' component={ErrorNotFoundPage} />
|
<Route path='*' component={ErrorNotFoundPage} />
|
||||||
</Route>
|
</Route>
|
||||||
)
|
)
|
||||||
|
@ -78,7 +78,7 @@ let ROUTES = {
|
|||||||
headerTitle='COLLECTION'
|
headerTitle='COLLECTION'
|
||||||
disableOn='noPieces' />
|
disableOn='noPieces' />
|
||||||
<Route path='editions/:editionId' component={EditionContainer} />
|
<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='pieces/:pieceId' component={CylandPieceContainer} />
|
||||||
<Route path='*' component={ErrorNotFoundPage} />
|
<Route path='*' component={ErrorNotFoundPage} />
|
||||||
</Route>
|
</Route>
|
||||||
@ -114,7 +114,7 @@ let ROUTES = {
|
|||||||
disableOn='noPieces' />
|
disableOn='noPieces' />
|
||||||
<Route path='pieces/:pieceId' component={PieceContainer} />
|
<Route path='pieces/:pieceId' component={PieceContainer} />
|
||||||
<Route path='editions/:editionId' component={EditionContainer} />
|
<Route path='editions/:editionId' component={EditionContainer} />
|
||||||
<Route path='verify' component={CoaVerifyContainer} />
|
<Route path='coa_verify' component={CoaVerifyContainer} />
|
||||||
<Route path='*' component={ErrorNotFoundPage} />
|
<Route path='*' component={ErrorNotFoundPage} />
|
||||||
</Route>
|
</Route>
|
||||||
),
|
),
|
||||||
@ -159,7 +159,7 @@ let ROUTES = {
|
|||||||
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(IkonotvContractNotifications)} />
|
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(IkonotvContractNotifications)} />
|
||||||
<Route path='pieces/:pieceId' component={IkonotvPieceContainer} />
|
<Route path='pieces/:pieceId' component={IkonotvPieceContainer} />
|
||||||
<Route path='editions/:editionId' component={EditionContainer} />
|
<Route path='editions/:editionId' component={EditionContainer} />
|
||||||
<Route path='verify' component={CoaVerifyContainer} />
|
<Route path='coa_verify' component={CoaVerifyContainer} />
|
||||||
<Route path='*' component={ErrorNotFoundPage} />
|
<Route path='*' component={ErrorNotFoundPage} />
|
||||||
</Route>
|
</Route>
|
||||||
),
|
),
|
||||||
@ -196,7 +196,7 @@ let ROUTES = {
|
|||||||
disableOn='noPieces' />
|
disableOn='noPieces' />
|
||||||
<Route path='pieces/:pieceId' component={MarketPieceContainer} />
|
<Route path='pieces/:pieceId' component={MarketPieceContainer} />
|
||||||
<Route path='editions/:editionId' component={MarketEditionContainer} />
|
<Route path='editions/:editionId' component={MarketEditionContainer} />
|
||||||
<Route path='verify' component={CoaVerifyContainer} />
|
<Route path='coa_verify' component={CoaVerifyContainer} />
|
||||||
<Route path='*' component={ErrorNotFoundPage} />
|
<Route path='*' component={ErrorNotFoundPage} />
|
||||||
</Route>
|
</Route>
|
||||||
),
|
),
|
||||||
@ -233,7 +233,7 @@ let ROUTES = {
|
|||||||
disableOn='noPieces' />
|
disableOn='noPieces' />
|
||||||
<Route path='pieces/:pieceId' component={MarketPieceContainer} />
|
<Route path='pieces/:pieceId' component={MarketPieceContainer} />
|
||||||
<Route path='editions/:editionId' component={MarketEditionContainer} />
|
<Route path='editions/:editionId' component={MarketEditionContainer} />
|
||||||
<Route path='verify' component={CoaVerifyContainer} />
|
<Route path='coa_verify' component={CoaVerifyContainer} />
|
||||||
<Route path='*' component={ErrorNotFoundPage} />
|
<Route path='*' component={ErrorNotFoundPage} />
|
||||||
</Route>
|
</Route>
|
||||||
)
|
)
|
||||||
|
20
package.json
20
package.json
@ -12,8 +12,22 @@
|
|||||||
"postinstall": "npm run build",
|
"postinstall": "npm run build",
|
||||||
"build": "gulp build --production",
|
"build": "gulp build --production",
|
||||||
"start": "node server.js",
|
"start": "node server.js",
|
||||||
"test": "mocha",
|
"test": "npm run sauce-test",
|
||||||
"tunnel": "node test/tunnel.js"
|
"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-test:base || true",
|
||||||
|
"vi-test:base": "npm run vi-clean && gemini test -c ./test/gemini/.gemini.yml --reporter html --reporter vflat",
|
||||||
|
"vi-test:all": "npm run vi-test",
|
||||||
|
"vi-test:main": "npm run vi-test:base -- --browser MainDesktop --browser MainMobile || true",
|
||||||
|
"vi-test:whitelabel": "GEMINI_BROWSERS='CcDesktop, CcMobile, CylandDesktop, CylandMobile, IkonotvDesktop, IkonotvMobile, LumenusDesktop, LumenusMobile, 23viviDesktop, 23viviMobile' npm run vi-test:base || true",
|
||||||
|
"vi-test:cc": "npm run vi-test:base -- --browser CcDesktop --browser CcMobile",
|
||||||
|
"vi-test:cyland": "npm run vi-test:base -- --browser CylandDesktop --browser CylandMobile || true",
|
||||||
|
"vi-test:ikonotv": "npm run vi-test:base -- --browser IkonotvDesktop --browser IkonotvMobile || true",
|
||||||
|
"vi-test:lumenus": "npm run vi-test:base -- --browser LumenusDesktop --browser LumenusMobile || true",
|
||||||
|
"vi-test:23vivi": "npm run vi-test:base -- --browser 23viviDesktop --browser 23viviMobile || true"
|
||||||
},
|
},
|
||||||
"browser": {
|
"browser": {
|
||||||
"fineUploader": "./js/components/ascribe_uploader/vendor/s3.fine-uploader.js"
|
"fineUploader": "./js/components/ascribe_uploader/vendor/s3.fine-uploader.js"
|
||||||
@ -42,8 +56,10 @@
|
|||||||
"chai-as-promised": "^5.1.0",
|
"chai-as-promised": "^5.1.0",
|
||||||
"colors": "^1.1.2",
|
"colors": "^1.1.2",
|
||||||
"dotenv": "^1.2.0",
|
"dotenv": "^1.2.0",
|
||||||
|
"gemini": "^2.1.0",
|
||||||
"jest-cli": "^0.4.0",
|
"jest-cli": "^0.4.0",
|
||||||
"mocha": "^2.3.4",
|
"mocha": "^2.3.4",
|
||||||
|
"phantomjs2": "^2.0.2",
|
||||||
"sauce-connect-launcher": "^0.13.0",
|
"sauce-connect-launcher": "^0.13.0",
|
||||||
"wd": "^0.4.0"
|
"wd": "^0.4.0"
|
||||||
},
|
},
|
||||||
|
133
test/gemini/.gemini.yml
Normal file
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
|
208
test/gemini/README.md
Normal file
208
test/gemini/README.md
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
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/`. For now, the tests use the environment defined in
|
||||||
|
`onion/test/gemini/tests/environment.js` for which user, piece, and edition to run tests against. In the future, it'd be
|
||||||
|
nice if we had some db scripts that we could use to populate a test db for these regression tests.
|
||||||
|
|
||||||
|
**It would also be nice if we kept the whitelabels up to date, so if you add one, please also test (at least) its landing
|
||||||
|
page.**
|
||||||
|
|
||||||
|
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, The repo had a short test script that can be run
|
||||||
|
with PhantomJS to check if it can access the web app and log in. Find `onion/test/phantomjs/launch_app_and_login.js` in
|
||||||
|
the repo's history, restore it, and then run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In root /onion folder
|
||||||
|
phantomjs test/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
|
35
test/gemini/tests/environment.js
Normal file
35
test/gemini/tests/environment.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const MAIN_USER = {
|
||||||
|
email: 'dimi@mailinator.com',
|
||||||
|
password: '0000000000'
|
||||||
|
};
|
||||||
|
const MAIN_PIECE_ID = '12374';
|
||||||
|
const MAIN_EDITION_ID = '14gw9x3VA9oJaxp4cHaAuK2bvJzvEj4Xvc';
|
||||||
|
|
||||||
|
const TIMEOUTS = {
|
||||||
|
SHORT: 3000,
|
||||||
|
NORMAL: 5000,
|
||||||
|
LONG: 10000,
|
||||||
|
SUPER_DUPER_EXTRA_LONG: 30000
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('================== Test environment ==================\n');
|
||||||
|
console.log('Main user:');
|
||||||
|
console.log(` Email: ${MAIN_USER.email}`);
|
||||||
|
console.log(` Password: ${MAIN_USER.password}\n`);
|
||||||
|
console.log(`Main piece: ${MAIN_PIECE_ID}`);
|
||||||
|
console.log(`Main edition: ${MAIN_EDITION_ID}\n`);
|
||||||
|
console.log('Timeouts:');
|
||||||
|
console.log(` Short: ${TIMEOUTS.SHORT}`);
|
||||||
|
console.log(` Normal: ${TIMEOUTS.NORMAL}\n`);
|
||||||
|
console.log(` Long: ${TIMEOUTS.LONG}\n`);
|
||||||
|
console.log(` Super super extra long: ${TIMEOUTS.SUPER_DUPER_EXTRA_LONG}\n`);
|
||||||
|
console.log('========================================================\n');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
MAIN_USER,
|
||||||
|
MAIN_PIECE_ID,
|
||||||
|
MAIN_EDITION_ID,
|
||||||
|
TIMEOUTS
|
||||||
|
};
|
218
test/gemini/tests/main/authenticated.js
Normal file
218
test/gemini/tests/main/authenticated.js
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const gemini = require('gemini');
|
||||||
|
const environment = require('../environment');
|
||||||
|
const MAIN_USER = environment.MAIN_USER;
|
||||||
|
const TIMEOUTS = environment.TIMEOUTS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suite of tests against routes that require the user to be authenticated.
|
||||||
|
*/
|
||||||
|
gemini.suite('Authenticated', (suite) => {
|
||||||
|
suite
|
||||||
|
.setUrl('/collection')
|
||||||
|
.setCaptureElements('.ascribe-body')
|
||||||
|
.before((actions, find) => {
|
||||||
|
// This will be called before every nested suite begins unless that suite
|
||||||
|
// also defines a `.before()`
|
||||||
|
// FIXME: use a more generic class for this, like just '.app',
|
||||||
|
// when we can use this file with the whitelabels
|
||||||
|
actions.waitForElementToShow('.ascribe-default-app', TIMEOUTS.NORMAL);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Suite just to log us in before any other suites run
|
||||||
|
gemini.suite('Login', (loginSuite) => {
|
||||||
|
loginSuite
|
||||||
|
.setUrl('/login')
|
||||||
|
.ignoreElements('.ascribe-body')
|
||||||
|
.capture('logged in', (actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
|
||||||
|
|
||||||
|
actions.sendKeys(find('.ascribe-login-wrapper input[name=email]'), MAIN_USER.email);
|
||||||
|
actions.sendKeys(find('.ascribe-login-wrapper input[name=password]'), MAIN_USER.password);
|
||||||
|
actions.click(find('.ascribe-login-wrapper button[type=submit]'));
|
||||||
|
|
||||||
|
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Header-desktop', (headerSuite) => {
|
||||||
|
headerSuite
|
||||||
|
.setCaptureElements('nav.navbar .container')
|
||||||
|
// Ignore Cyland's logo as it's a gif
|
||||||
|
.ignoreElements('.client--cyland img.img-brand')
|
||||||
|
.skip(/Mobile/)
|
||||||
|
.before((actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
|
||||||
|
})
|
||||||
|
.capture('desktop header');
|
||||||
|
|
||||||
|
gemini.suite('User dropdown', (headerUserSuite) => {
|
||||||
|
headerUserSuite
|
||||||
|
.setCaptureElements('#nav-route-user-dropdown ~ .dropdown-menu')
|
||||||
|
.capture('expanded user dropdown', (actions, find) => {
|
||||||
|
actions.click(find('#nav-route-user-dropdown'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Notification dropdown', (headerNotificationSuite) => {
|
||||||
|
headerNotificationSuite
|
||||||
|
.setCaptureElements('#header-notification-dropdown ~ .dropdown-menu')
|
||||||
|
.capture('expanded notifications dropdown', (actions, find) => {
|
||||||
|
actions.click(find('#header-notification-dropdown'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test for the collapsed header in mobile
|
||||||
|
gemini.suite('Header-mobile', (headerMobileSuite) => {
|
||||||
|
headerMobileSuite
|
||||||
|
.setCaptureElements('nav.navbar .container')
|
||||||
|
// Ignore Cyland's logo as it's a gif
|
||||||
|
.ignoreElements('.client--cyland img.img-brand')
|
||||||
|
.skip(/Desktop/)
|
||||||
|
.before((actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
|
||||||
|
})
|
||||||
|
.capture('mobile header')
|
||||||
|
.capture('expanded mobile header', (actions, find) => {
|
||||||
|
actions.click(find('nav.navbar .navbar-toggle'));
|
||||||
|
// Wait for the header to expand
|
||||||
|
actions.wait(500);
|
||||||
|
})
|
||||||
|
.capture('expanded user dropdown', (actions, find) => {
|
||||||
|
actions.click(find('#nav-route-user-dropdown'));
|
||||||
|
})
|
||||||
|
.capture('expanded notifications dropdown', (actions, find) => {
|
||||||
|
actions.click(find('#header-notification-dropdown'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Collection', (collectionSuite) => {
|
||||||
|
collectionSuite
|
||||||
|
.setCaptureElements('.ascribe-accordion-list')
|
||||||
|
.before((actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
|
||||||
|
// Wait for the images to load
|
||||||
|
// FIXME: unfortuntately gemini doesn't support ignoring multiple elements from a single selector
|
||||||
|
// so we're forced to wait and hope that the images will all finish loading after 5s.
|
||||||
|
// We could also change the thumbnails with JS, but setting up a test user is probably easier.
|
||||||
|
actions.wait(TIMEOUTS.NORMAL);
|
||||||
|
})
|
||||||
|
.capture('collection')
|
||||||
|
.capture('expanded edition in collection', (actions, find) => {
|
||||||
|
actions.click(find('.ascribe-accordion-list-item .ascribe-accordion-list-item-edition-widget'));
|
||||||
|
// Wait for editions to load
|
||||||
|
actions.waitForElementToShow('.ascribe-accordion-list-item-table', TIMEOUTS.LONG);
|
||||||
|
})
|
||||||
|
|
||||||
|
gemini.suite('Collection placeholder', (collectionPlaceholderSuite) => {
|
||||||
|
collectionPlaceholderSuite
|
||||||
|
.setCaptureElements('.ascribe-accordion-list-placeholder')
|
||||||
|
.capture('collection empty search', (actions, find) => {
|
||||||
|
actions.sendKeys(find('.ascribe-piece-list-toolbar .search-bar input[type="text"]'), 'no search result');
|
||||||
|
actions.waitForElementToShow('.ascribe-accordion-list-placeholder', TIMEOUTS.NORMAL);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('PieceListBulkModal', (pieceListBulkModalSuite) => {
|
||||||
|
pieceListBulkModalSuite
|
||||||
|
.setCaptureElements('.piece-list-bulk-modal')
|
||||||
|
.capture('items selected', (actions, find) => {
|
||||||
|
actions.click(find('.ascribe-accordion-list-item .ascribe-accordion-list-item-edition-widget'));
|
||||||
|
// Wait for editions to load
|
||||||
|
actions.waitForElementToShow('.ascribe-accordion-list-item-table', TIMEOUTS.NORMAL);
|
||||||
|
|
||||||
|
actions.click('.ascribe-table thead tr input[type="checkbox"]');
|
||||||
|
actions.waitForElementToShow('.piece-list-bulk-modal');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('PieceListToolbar', (pieceListToolbarSuite) => {
|
||||||
|
pieceListToolbarSuite
|
||||||
|
.setCaptureElements('.ascribe-piece-list-toolbar')
|
||||||
|
.capture('piece list toolbar')
|
||||||
|
.capture('piece list toolbar search filled', (actions, find) => {
|
||||||
|
actions.sendKeys(find('.ascribe-piece-list-toolbar .search-bar input[type="text"]'), 'search text');
|
||||||
|
actions.waitForElementToShow('.ascribe-piece-list-toolbar .search-bar .icon-ascribe-search', TIMEOUTS.NORMAL);
|
||||||
|
})
|
||||||
|
|
||||||
|
gemini.suite('Order widget dropdown', (pieceListToolbarOrderWidgetSuite) => {
|
||||||
|
pieceListToolbarOrderWidgetSuite
|
||||||
|
.setCaptureElements('#ascribe-piece-list-toolbar-order-widget-dropdown',
|
||||||
|
'#ascribe-piece-list-toolbar-order-widget-dropdown ~ .dropdown-menu')
|
||||||
|
.capture('expanded order dropdown', (actions, find) => {
|
||||||
|
actions.click(find('#ascribe-piece-list-toolbar-order-widget-dropdown'));
|
||||||
|
|
||||||
|
// Wait as the dropdown screenshot still includes the collection in the background
|
||||||
|
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Filter widget dropdown', (pieceListToolbarFilterWidgetSuite) => {
|
||||||
|
pieceListToolbarFilterWidgetSuite
|
||||||
|
.setCaptureElements('#ascribe-piece-list-toolbar-filter-widget-dropdown',
|
||||||
|
'#ascribe-piece-list-toolbar-filter-widget-dropdown ~ .dropdown-menu')
|
||||||
|
.capture('expanded filter dropdown', (actions, find) => {
|
||||||
|
actions.click(find('#ascribe-piece-list-toolbar-filter-widget-dropdown'));
|
||||||
|
|
||||||
|
// Wait as the dropdown screenshot still includes the collection in the background
|
||||||
|
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Register work', (registerSuite) => {
|
||||||
|
registerSuite
|
||||||
|
.setUrl('/register_piece')
|
||||||
|
.capture('register work', (actions, find) => {
|
||||||
|
// The uploader options are only rendered after the user is fetched, so
|
||||||
|
// we have to wait for it here
|
||||||
|
actions.waitForElementToShow('.file-drag-and-drop-dialog .present-options', TIMEOUTS.NORMAL);
|
||||||
|
})
|
||||||
|
.capture('register work filled', (actions, find) => {
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name="artist_name"]'), 'artist name');
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name="title"]'), 'title');
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name="date_created"]'), 'date created');
|
||||||
|
})
|
||||||
|
.capture('register work filled with editions', (actions, find) => {
|
||||||
|
actions.click(find('.ascribe-form input[type="checkbox"] ~ .checkbox'));
|
||||||
|
actions.wait(500);
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name="num_editions"]'), '50');
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Register work hash', (registerHashSuite) => {
|
||||||
|
registerHashSuite
|
||||||
|
.setUrl('/register_piece?method=hash')
|
||||||
|
.capture('register work hash method');
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Register work upload', (registerUploadSuite) => {
|
||||||
|
registerUploadSuite
|
||||||
|
.setUrl('/register_piece?method=upload')
|
||||||
|
.capture('register work upload method');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('User settings', (userSettingsSuite) => {
|
||||||
|
userSettingsSuite
|
||||||
|
.setUrl('/settings')
|
||||||
|
.before((actions, find) => {
|
||||||
|
// This will be called before every nested suite begins unless that suite
|
||||||
|
// also defines a `.before()`
|
||||||
|
actions.waitForElementToShow('.settings-container', TIMEOUTS.NORMAL);
|
||||||
|
})
|
||||||
|
.capture('user settings');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Suite just to log out after suites have run
|
||||||
|
gemini.suite('Log out', (logoutSuite) => {
|
||||||
|
logoutSuite
|
||||||
|
.setUrl('/logout')
|
||||||
|
.ignoreElements('.ascribe-body')
|
||||||
|
.capture('logout', (actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-login-wrapper', TIMEOUTS.LONG);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
148
test/gemini/tests/main/basic.js
Normal file
148
test/gemini/tests/main/basic.js
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const gemini = require('gemini');
|
||||||
|
const environment = require('../environment');
|
||||||
|
const MAIN_USER = environment.MAIN_USER;
|
||||||
|
const TIMEOUTS = environment.TIMEOUTS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic suite of tests against routes that do not require the user to be authenticated.
|
||||||
|
*/
|
||||||
|
gemini.suite('Basic', (suite) => {
|
||||||
|
suite
|
||||||
|
.setUrl('/login')
|
||||||
|
.setCaptureElements('.ascribe-body')
|
||||||
|
.before((actions, find) => {
|
||||||
|
// This will be called before every nested suite begins unless that suite
|
||||||
|
// also defines a `.before()`
|
||||||
|
// FIXME: use a more generic class for this, like just '.ascribe-app'
|
||||||
|
actions.waitForElementToShow('.ascribe-default-app', TIMEOUTS.NORMAL);
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Header-desktop', (headerSuite) => {
|
||||||
|
headerSuite
|
||||||
|
.setCaptureElements('nav.navbar .container')
|
||||||
|
.skip(/Mobile/)
|
||||||
|
.capture('desktop header', (actions, find) => {
|
||||||
|
actions.waitForElementToShow('nav.navbar .container', TIMEOUTS.NORMAL);
|
||||||
|
})
|
||||||
|
.capture('hover on active item', (actions, find) => {
|
||||||
|
const activeItem = find('nav.navbar li.active');
|
||||||
|
actions.mouseMove(activeItem);
|
||||||
|
})
|
||||||
|
.capture('hover on inactive item', (actions, find) => {
|
||||||
|
const inactiveItem = find('nav.navbar li:not(.active)');
|
||||||
|
actions.mouseMove(inactiveItem);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test for the collapsed header in mobile
|
||||||
|
gemini.suite('Header-mobile', (headerMobileSuite) => {
|
||||||
|
headerMobileSuite
|
||||||
|
.setCaptureElements('nav.navbar .container')
|
||||||
|
.skip(/Desktop/)
|
||||||
|
.capture('mobile header', (actions, find) => {
|
||||||
|
actions.waitForElementToShow('nav.navbar .container', TIMEOUTS.NORMAL);
|
||||||
|
})
|
||||||
|
.capture('expanded mobile header', (actions, find) => {
|
||||||
|
actions.click(find('nav.navbar .navbar-toggle'));
|
||||||
|
// Wait for the header to expand
|
||||||
|
actions.wait(500);
|
||||||
|
})
|
||||||
|
.capture('hover on expanded mobile header item', (actions, find) => {
|
||||||
|
actions.mouseMove(find('nav.navbar li'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Footer', (footerSuite) => {
|
||||||
|
footerSuite
|
||||||
|
.setCaptureElements('.ascribe-footer')
|
||||||
|
.capture('footer', (actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-footer', TIMEOUTS.NORMAL);
|
||||||
|
})
|
||||||
|
.capture('hover on footer item', (actions, find) => {
|
||||||
|
const footerItem = find('.ascribe-footer a:not(.social)');
|
||||||
|
actions.mouseMove(footerItem);
|
||||||
|
})
|
||||||
|
.capture('hover on footer social item', (actions, find) => {
|
||||||
|
const footerSocialItem = find('.ascribe-footer a.social')
|
||||||
|
actions.mouseMove(footerSocialItem);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Login', (loginSuite) => {
|
||||||
|
loginSuite
|
||||||
|
.capture('login', (actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
|
||||||
|
})
|
||||||
|
.capture('hover on login submit', (actions, find) => {
|
||||||
|
actions.mouseMove(find('.ascribe-form button[type=submit]'));
|
||||||
|
})
|
||||||
|
.capture('hover on sign up link', (actions, find) => {
|
||||||
|
actions.mouseMove(find('.ascribe-login-text a[href="/signup"]'));
|
||||||
|
})
|
||||||
|
.capture('login form filled with focus', (actions, find) => {
|
||||||
|
const emailInput = find('.ascribe-form input[name=email]');
|
||||||
|
|
||||||
|
// Remove hover from sign up link
|
||||||
|
actions.click(emailInput);
|
||||||
|
|
||||||
|
actions.sendKeys(emailInput, MAIN_USER.email);
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name=password]'), MAIN_USER.password);
|
||||||
|
})
|
||||||
|
.capture('login form filled', (actions, find) => {
|
||||||
|
actions.click(find('.ascribe-form-header'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Sign up', (signUpSuite) => {
|
||||||
|
signUpSuite
|
||||||
|
.setUrl('/signup')
|
||||||
|
.capture('sign up', (actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
|
||||||
|
})
|
||||||
|
.capture('sign up form filled with focus', (actions, find) => {
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name=email]'), MAIN_USER.email);
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name=password]'), MAIN_USER.password);
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), MAIN_USER.password);
|
||||||
|
})
|
||||||
|
.capture('sign up form filled with check', (actions, find) => {
|
||||||
|
actions.click(find('.ascribe-form input[type="checkbox"] ~ .checkbox'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Password reset', (passwordResetSuite) => {
|
||||||
|
passwordResetSuite
|
||||||
|
.setUrl('/password_reset')
|
||||||
|
.capture('password reset', (actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
|
||||||
|
})
|
||||||
|
.capture('password reset form filled with focus', (actions, find) => {
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name="email"]'), MAIN_USER.email);
|
||||||
|
})
|
||||||
|
.capture('password reset form filled', (actions, find) => {
|
||||||
|
actions.click(find('.ascribe-form-header'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Coa verify', (coaVerifySuite) => {
|
||||||
|
coaVerifySuite
|
||||||
|
.setUrl('/coa_verify')
|
||||||
|
.capture('coa verify', (actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
|
||||||
|
})
|
||||||
|
.capture('coa verify form filled with focus', (actions, find) => {
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name="message"]'), 'sample text');
|
||||||
|
actions.sendKeys(find('.ascribe-form .ascribe-property-wrapper:nth-of-type(2) textarea'), 'sample signature');
|
||||||
|
})
|
||||||
|
.capture('coa verify form filled', (actions, find) => {
|
||||||
|
actions.click(find('.ascribe-login-header'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Not found', (notFoundSuite) => {
|
||||||
|
notFoundSuite
|
||||||
|
.setUrl('/not_found_page')
|
||||||
|
.capture('not found page');
|
||||||
|
});
|
||||||
|
});
|
134
test/gemini/tests/main/detail.js
Normal file
134
test/gemini/tests/main/detail.js
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const gemini = require('gemini');
|
||||||
|
const environment = require('../environment');
|
||||||
|
const MAIN_USER = environment.MAIN_USER;
|
||||||
|
const TIMEOUTS = environment.TIMEOUTS;
|
||||||
|
|
||||||
|
const pieceUrl = `/pieces/${environment.MAIN_PIECE_ID}`;
|
||||||
|
const editionUrl = `/editions/${environment.MAIN_EDITION_ID}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suite of tests against the piece and edition routes.
|
||||||
|
* Tests include accessing the piece / edition as the owner or as another user
|
||||||
|
* (we can just use an anonymous user in this case).
|
||||||
|
*/
|
||||||
|
gemini.suite('Work detail', (suite) => {
|
||||||
|
suite
|
||||||
|
.setCaptureElements('.ascribe-body')
|
||||||
|
.before((actions, find) => {
|
||||||
|
// This will be called before every nested suite begins unless that suite
|
||||||
|
// also defines a `.before()`
|
||||||
|
// FIXME: use a more generic class for this, like just '.app',
|
||||||
|
// when we can use this file with the whitelabels
|
||||||
|
actions.waitForElementToShow('.ascribe-default-app', TIMEOUTS.NORMAL);
|
||||||
|
|
||||||
|
// Wait for the social media buttons to appear
|
||||||
|
actions.waitForElementToShow('.ascribe-social-button-list .fb-share-button iframe', TIMEOUTS.SUPER_DUPER_EXTRA_LONG);
|
||||||
|
actions.waitForElementToShow('.ascribe-social-button-list .twitter-share-button', TIMEOUTS.SUPER_DUPER_EXTRA_LONG);
|
||||||
|
actions.waitForElementToShow('.ascribe-media-player', TIMEOUTS.LONG);
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Basic piece', (basicPieceSuite) => {
|
||||||
|
basicPieceSuite
|
||||||
|
.setUrl(pieceUrl)
|
||||||
|
.capture('basic piece')
|
||||||
|
|
||||||
|
gemini.suite('Shmui', (shmuiSuite) => {
|
||||||
|
shmuiSuite.
|
||||||
|
setCaptureElements('.shmui-wrap')
|
||||||
|
.capture('shmui', (actions, find) => {
|
||||||
|
actions.click(find('.ascribe-media-player'));
|
||||||
|
actions.waitForElementToShow('.shmui-wrap:not(.loading)', TIMEOUTS.SUPER_DUPER_EXTRA_LONG);
|
||||||
|
// Wait for the transition to end
|
||||||
|
actions.wait(1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Basic edition', (basicEditionSuite) => {
|
||||||
|
basicEditionSuite
|
||||||
|
.setUrl(editionUrl)
|
||||||
|
.capture('basic edition');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Suite just to log us in before any other suites run
|
||||||
|
gemini.suite('Login', (loginSuite) => {
|
||||||
|
loginSuite
|
||||||
|
.setUrl('/login')
|
||||||
|
.ignoreElements('.ascribe-body')
|
||||||
|
.before((actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-default-app', TIMEOUTS.NORMAL);
|
||||||
|
})
|
||||||
|
.capture('logged in', (actions, find) => {
|
||||||
|
actions.sendKeys(find('.ascribe-login-wrapper input[name=email]'), MAIN_USER.email);
|
||||||
|
actions.sendKeys(find('.ascribe-login-wrapper input[name=password]'), MAIN_USER.password);
|
||||||
|
actions.click(find('.ascribe-login-wrapper button[type=submit]'));
|
||||||
|
|
||||||
|
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Authorized piece', (authorizedPieceSuite) => {
|
||||||
|
authorizedPieceSuite
|
||||||
|
.setUrl(pieceUrl)
|
||||||
|
.capture('authorized piece');
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Authorized edition', (authorizedEditionSuite) => {
|
||||||
|
authorizedEditionSuite
|
||||||
|
.setUrl(editionUrl)
|
||||||
|
.capture('authorized edition')
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Detail action buttons', (detailActionButtonSuite) => {
|
||||||
|
detailActionButtonSuite
|
||||||
|
.setUrl(editionUrl)
|
||||||
|
.capture('hover on action button', (actions, find) => {
|
||||||
|
actions.mouseMove(find('.ascribe-detail-property .ascribe-button-list button.btn-default'));
|
||||||
|
})
|
||||||
|
.capture('hover on delete button', (actions, find) => {
|
||||||
|
actions.mouseMove(find('.ascribe-detail-property .ascribe-button-list button.btn-tertiary'));
|
||||||
|
})
|
||||||
|
.capture('hover on info button', (actions, find) => {
|
||||||
|
actions.mouseMove(find('.ascribe-detail-property .ascribe-button-list button.glyphicon-question-sign'));
|
||||||
|
})
|
||||||
|
.capture('expand info text', (actions, find) => {
|
||||||
|
actions.click(find('.ascribe-detail-property .ascribe-button-list button.glyphicon-question-sign'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Action form modal', (actionFormModalSuite) => {
|
||||||
|
actionFormModalSuite
|
||||||
|
.setUrl(editionUrl)
|
||||||
|
.setCaptureElements('.modal-dialog')
|
||||||
|
.capture('open email form', (actions, find) => {
|
||||||
|
// Add class names to make the action buttons easier to select
|
||||||
|
actions.executeJS(function (window) {
|
||||||
|
var actionButtons = window.document.querySelectorAll('.ascribe-detail-property .ascribe-button-list button.btn-default');
|
||||||
|
for (var ii = 0; ii < actionButtons.length; ++ii) {
|
||||||
|
if (actionButtons[ii].textContent) {
|
||||||
|
actionButtons[ii].className += ' ascribe-action-button-' + actionButtons[ii].textContent.toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
actions.click(find('.ascribe-detail-property .ascribe-button-list button.ascribe-action-button-email'));
|
||||||
|
|
||||||
|
// Wait for transition
|
||||||
|
actions.wait(1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Suite just to log out after suites have run
|
||||||
|
gemini.suite('Log out', (logoutSuite) => {
|
||||||
|
logoutSuite
|
||||||
|
.setUrl('/logout')
|
||||||
|
.ignoreElements('.ascribe-body')
|
||||||
|
.before((actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-default-app', TIMEOUTS.NORMAL);
|
||||||
|
})
|
||||||
|
.capture('logout', (actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-login-wrapper', TIMEOUTS.LONG);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
29
test/gemini/tests/whitelabel/23vivi/23vivi.js
Normal file
29
test/gemini/tests/whitelabel/23vivi/23vivi.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const gemini = require('gemini');
|
||||||
|
const environment = require('../../environment');
|
||||||
|
const TIMEOUTS = environment.TIMEOUTS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suite of tests against 23vivi specific routes
|
||||||
|
*/
|
||||||
|
gemini.suite('23vivi', (suite) => {
|
||||||
|
suite
|
||||||
|
//TODO: maybe this should be changed to .ascribe-body once the PR that does this is merged
|
||||||
|
.setCaptureElements('.ascribe-wallet-app')
|
||||||
|
.before((actions, find) => {
|
||||||
|
// This will be called before every nested suite begins
|
||||||
|
actions.waitForElementToShow('.ascribe-wallet-app', TIMEOUTS.NORMAL);
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Landing', (landingSuite) => {
|
||||||
|
landingSuite
|
||||||
|
.setUrl('/')
|
||||||
|
.capture('landing', (actions, find) => {
|
||||||
|
// Wait for the logo to appear
|
||||||
|
actions.waitForElementToShow('.vivi23-landing--header-logo', TIMEOUTS.LONG);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: add more tests for market specific pages after authentication
|
||||||
|
});
|
30
test/gemini/tests/whitelabel/cyland/cyland.js
Normal file
30
test/gemini/tests/whitelabel/cyland/cyland.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const gemini = require('gemini');
|
||||||
|
const environment = require('../../environment');
|
||||||
|
const TIMEOUTS = environment.TIMEOUTS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suite of tests against Cyland specific routes
|
||||||
|
*/
|
||||||
|
gemini.suite('Cyland', (suite) => {
|
||||||
|
suite
|
||||||
|
//TODO: maybe this should be changed to .ascribe-body once the PR that does this is merged
|
||||||
|
.setCaptureElements('.ascribe-wallet-app')
|
||||||
|
.before((actions, find) => {
|
||||||
|
// This will be called before every nested suite begins
|
||||||
|
actions.waitForElementToShow('.ascribe-wallet-app', TIMEOUTS.NORMAL);
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Landing', (landingSuite) => {
|
||||||
|
landingSuite
|
||||||
|
.setUrl('/')
|
||||||
|
// Ignore Cyland's logo as it's a gif
|
||||||
|
.ignoreElements('.cyland-landing img')
|
||||||
|
.capture('landing', (actions, find) => {
|
||||||
|
actions.waitForElementToShow('.cyland-landing img', TIMEOUTS.LONG);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: add more tests for cyland specific pages after authentication
|
||||||
|
});
|
98
test/gemini/tests/whitelabel/ikonotv/ikonotv.js
Normal file
98
test/gemini/tests/whitelabel/ikonotv/ikonotv.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const gemini = require('gemini');
|
||||||
|
const environment = require('../../environment');
|
||||||
|
const MAIN_USER = environment.MAIN_USER;
|
||||||
|
const TIMEOUTS = environment.TIMEOUTS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suite of tests against Cyland specific routes
|
||||||
|
*/
|
||||||
|
gemini.suite('Ikonotv', (suite) => {
|
||||||
|
suite
|
||||||
|
//TODO: maybe this should be changed to .ascribe-body once the PR that does this is merged
|
||||||
|
.setCaptureElements('.ascribe-wallet-app')
|
||||||
|
.before((actions, find) => {
|
||||||
|
// This will be called before every nested suite begins
|
||||||
|
actions.waitForElementToShow('.ascribe-wallet-app', TIMEOUTS.NORMAL);
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Landing', (landingSuite) => {
|
||||||
|
landingSuite
|
||||||
|
.setUrl('/')
|
||||||
|
// Gemini complains if we try to capture the entire app for Ikonotv's landing page for some reason
|
||||||
|
.setCaptureElements('.ikonotv-landing')
|
||||||
|
.setTolerance(5)
|
||||||
|
.capture('landing', (actions, find) => {
|
||||||
|
// Stop background animation
|
||||||
|
actions.executeJS(function (window) {
|
||||||
|
var landingBackground = window.document.querySelector('.client--ikonotv .route--landing');
|
||||||
|
landingBackground.style.animation = 'none';
|
||||||
|
landingBackground.style.webkitAnimation = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for logo to appear
|
||||||
|
actions.waitForElementToShow('.ikonotv-landing header img', TIMEOUTS.LONG);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ikono needs its own set of tests for some pre-authorization pages to wait for
|
||||||
|
// its logo to appear
|
||||||
|
gemini.suite('Ikonotv basic', (suite) => {
|
||||||
|
suite
|
||||||
|
.setCaptureElements('.ascribe-wallet-app')
|
||||||
|
.before((actions, find) => {
|
||||||
|
// This will be called before every nested suite begins unless that suite
|
||||||
|
// also defines a `.before()`
|
||||||
|
// FIXME: use a more generic class for this, like just '.app',
|
||||||
|
// when we can use this file with the whitelabels
|
||||||
|
actions.waitForElementToShow('.ascribe-wallet-app', TIMEOUTS.NORMAL);
|
||||||
|
|
||||||
|
// Wait for the forms to appear
|
||||||
|
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
|
||||||
|
|
||||||
|
// Just use a dumb wait because the logo is set as a background image
|
||||||
|
actions.wait(TIMEOUTS.SHORT);
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Login', (loginSuite) => {
|
||||||
|
loginSuite
|
||||||
|
.setUrl('/login')
|
||||||
|
.capture('login')
|
||||||
|
.capture('hover on login submit', (actions, find) => {
|
||||||
|
actions.mouseMove(find('.ascribe-form button[type=submit]'));
|
||||||
|
})
|
||||||
|
.capture('hover on sign up link', (actions, find) => {
|
||||||
|
actions.mouseMove(find('.ascribe-login-text a[href="/signup"]'));
|
||||||
|
})
|
||||||
|
.capture('login form filled with focus', (actions, find) => {
|
||||||
|
const emailInput = find('.ascribe-form input[name=email]');
|
||||||
|
|
||||||
|
// Remove hover from sign up link
|
||||||
|
actions.click(emailInput);
|
||||||
|
|
||||||
|
actions.sendKeys(emailInput, MAIN_USER.email);
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name=password]'), MAIN_USER.password);
|
||||||
|
})
|
||||||
|
.capture('login form filled', (actions, find) => {
|
||||||
|
actions.click(find('.ascribe-form-header'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Sign up', (signUpSuite) => {
|
||||||
|
signUpSuite
|
||||||
|
.setUrl('/signup')
|
||||||
|
.capture('sign up')
|
||||||
|
.capture('sign up form filled with focus', (actions, find) => {
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name=email]'), MAIN_USER.email);
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name=password]'), MAIN_USER.password);
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), MAIN_USER.password);
|
||||||
|
})
|
||||||
|
.capture('sign up form filled with check', (actions, find) => {
|
||||||
|
actions.click(find('.ascribe-form input[type="checkbox"] ~ .checkbox'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: add more tests for ikonotv specific pages after authentication
|
||||||
|
});
|
29
test/gemini/tests/whitelabel/lumenus/lumenus.js
Normal file
29
test/gemini/tests/whitelabel/lumenus/lumenus.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const gemini = require('gemini');
|
||||||
|
const environment = require('../../environment');
|
||||||
|
const TIMEOUTS = environment.TIMEOUTS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suite of tests against lumenus specific routes
|
||||||
|
*/
|
||||||
|
gemini.suite('Lumenus', (suite) => {
|
||||||
|
suite
|
||||||
|
//TODO: maybe this should be changed to .ascribe-body once the PR that does this is merged
|
||||||
|
.setCaptureElements('.ascribe-wallet-app')
|
||||||
|
.before((actions, find) => {
|
||||||
|
// This will be called before every nested suite begins
|
||||||
|
actions.waitForElementToShow('.ascribe-wallet-app', TIMEOUTS.NORMAL);
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Landing', (landingSuite) => {
|
||||||
|
landingSuite
|
||||||
|
.setUrl('/')
|
||||||
|
.capture('landing', (actions, find) => {
|
||||||
|
// Wait for the logo to appear
|
||||||
|
actions.waitForElementToShow('.wp-landing-wrapper img', TIMEOUTS.LONG);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: add more tests for market specific pages after authentication
|
||||||
|
});
|
115
test/gemini/tests/whitelabel/shared/whitelabel_basic.js
Normal file
115
test/gemini/tests/whitelabel/shared/whitelabel_basic.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const gemini = require('gemini');
|
||||||
|
const environment = require('../../environment');
|
||||||
|
const MAIN_USER = environment.MAIN_USER;
|
||||||
|
const TIMEOUTS = environment.TIMEOUTS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic suite of tests against whitelabel routes that do not require authentication.
|
||||||
|
*/
|
||||||
|
gemini.suite('Whitelabel basic', (suite) => {
|
||||||
|
suite
|
||||||
|
.setCaptureElements('.ascribe-wallet-app > .container')
|
||||||
|
.before((actions, find) => {
|
||||||
|
// This will be called before every nested suite begins unless that suite
|
||||||
|
// also defines a `.before()`
|
||||||
|
// FIXME: use a more generic class for this, like just '.ascribe-app'
|
||||||
|
actions.waitForElementToShow('.ascribe-wallet-app', TIMEOUTS.NORMAL);
|
||||||
|
|
||||||
|
// Use a dumb wait in case we're still waiting for other assets, like fonts, to load
|
||||||
|
actions.wait(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Login', (loginSuite) => {
|
||||||
|
loginSuite
|
||||||
|
.setUrl('/login')
|
||||||
|
// See Ikono
|
||||||
|
.skip(/Ikono/)
|
||||||
|
.capture('login', (actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
|
||||||
|
// For some reason, the screenshots seem to keep catching the whitelabel login form
|
||||||
|
// on a refresh and without fonts loaded (maybe because they're the first tests run
|
||||||
|
// and the cache isn't hot yet?).
|
||||||
|
// Let's wait a bit and hope they load.
|
||||||
|
actions.wait(TIMEOUTS.SHORT);
|
||||||
|
})
|
||||||
|
.capture('hover on login submit', (actions, find) => {
|
||||||
|
actions.mouseMove(find('.ascribe-form button[type=submit]'));
|
||||||
|
})
|
||||||
|
.capture('hover on sign up link', (actions, find) => {
|
||||||
|
actions.mouseMove(find('.ascribe-login-text a[href="/signup"]'));
|
||||||
|
})
|
||||||
|
.capture('login form filled with focus', (actions, find) => {
|
||||||
|
const emailInput = find('.ascribe-form input[name=email]');
|
||||||
|
|
||||||
|
// Remove hover from sign up link
|
||||||
|
actions.click(emailInput);
|
||||||
|
|
||||||
|
actions.sendKeys(emailInput, MAIN_USER.email);
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name=password]'), MAIN_USER.password);
|
||||||
|
})
|
||||||
|
.capture('login form filled', (actions, find) => {
|
||||||
|
actions.click(find('.ascribe-form-header'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Sign up', (signUpSuite) => {
|
||||||
|
signUpSuite
|
||||||
|
.setUrl('/signup')
|
||||||
|
// See Ikono
|
||||||
|
.skip(/Ikono/)
|
||||||
|
.capture('sign up', (actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
|
||||||
|
// Wait in case the form reloads due to other assets loading
|
||||||
|
actions.wait(500);
|
||||||
|
})
|
||||||
|
.capture('sign up form filled with focus', (actions, find) => {
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name=email]'), MAIN_USER.email);
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name=password]'), MAIN_USER.password);
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), MAIN_USER.password);
|
||||||
|
})
|
||||||
|
.capture('sign up form filled with check', (actions, find) => {
|
||||||
|
actions.click(find('.ascribe-form input[type="checkbox"] ~ .checkbox'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Password reset', (passwordResetSuite) => {
|
||||||
|
passwordResetSuite
|
||||||
|
.setUrl('/password_reset')
|
||||||
|
.capture('password reset', (actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
|
||||||
|
// Wait in case the form reloads due to other assets loading
|
||||||
|
actions.wait(500);
|
||||||
|
})
|
||||||
|
.capture('password reset form filled with focus', (actions, find) => {
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name="email"]'), MAIN_USER.email);
|
||||||
|
})
|
||||||
|
.capture('password reset form filled', (actions, find) => {
|
||||||
|
actions.click(find('.ascribe-form-header'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Coa verify', (coaVerifySuite) => {
|
||||||
|
coaVerifySuite
|
||||||
|
.setUrl('/coa_verify')
|
||||||
|
.capture('coa verify', (actions, find) => {
|
||||||
|
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
|
||||||
|
// Wait in case the form reloads due to other assets loading
|
||||||
|
actions.wait(500);
|
||||||
|
})
|
||||||
|
.capture('coa verify form filled with focus', (actions, find) => {
|
||||||
|
actions.sendKeys(find('.ascribe-form input[name="message"]'), 'sample text');
|
||||||
|
actions.sendKeys(find('.ascribe-form .ascribe-property-wrapper:nth-of-type(2) textarea'), 'sample signature');
|
||||||
|
})
|
||||||
|
.capture('coa verify form filled', (actions, find) => {
|
||||||
|
actions.click(find('.ascribe-login-header'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gemini.suite('Not found', (notFoundSuite) => {
|
||||||
|
notFoundSuite
|
||||||
|
.setUrl('/not_found_page')
|
||||||
|
.capture('not found page');
|
||||||
|
});
|
||||||
|
});
|
@ -5,7 +5,7 @@ const wd = require('wd');
|
|||||||
const asserters = wd.asserters; // Commonly used asserters for async waits in the browser
|
const asserters = wd.asserters; // Commonly used asserters for async waits in the browser
|
||||||
const chai = require('chai');
|
const chai = require('chai');
|
||||||
const chaiAsPromised = require('chai-as-promised');
|
const chaiAsPromised = require('chai-as-promised');
|
||||||
const config = require('./config.js');
|
const config = require('../config.js');
|
||||||
|
|
||||||
chai.use(chaiAsPromised);
|
chai.use(chaiAsPromised);
|
||||||
chai.should();
|
chai.should();
|
Loading…
Reference in New Issue
Block a user