From db2b0276565436bf16022f321927d3d7d6631f5b Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 18 Dec 2015 15:00:37 +0100 Subject: [PATCH 01/27] Fix defaultValue not being initially set for InputDate --- js/components/ascribe_forms/input_date.js | 31 ++++++++++++++--------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/js/components/ascribe_forms/input_date.js b/js/components/ascribe_forms/input_date.js index 3e2892c0..d7e36b0d 100644 --- a/js/components/ascribe_forms/input_date.js +++ b/js/components/ascribe_forms/input_date.js @@ -17,10 +17,7 @@ let InputDate = React.createClass({ }, getInitialState() { - return { - value: null, - value_moment: null - }; + return this.getStateFromMoment(this.props.defaultValue); }, // InputDate needs to support setting a defaultValue from outside. @@ -28,20 +25,30 @@ let InputDate = React.createClass({ // to the outer Property componentWillReceiveProps(nextProps) { if(!this.state.value && !this.state.value_moment && nextProps.defaultValue) { - this.handleChange(this.props.defaultValue); + this.handleChange(nextProps.defaultValue); } }, - handleChange(date) { - let formattedDate = date.format('YYYY-MM-DD'); - this.setState({ - value: formattedDate, - value_moment: date - }); + getStateFromMoment(date) { + const state = {}; + if (date) { + state.value = date.format('YYYY-MM-DD'); + state.value_moment = date; + } + + return state; + }, + + handleChange(date) { + const newState = this.getStateFromMoment(date); + + this.setState(newState); + + // Propagate change up by faking event this.props.onChange({ target: { - value: formattedDate + value: newState.value } }); }, From 614bc3f74bba65ade05c8446d2b125e2de8a9735 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 18 Dec 2015 15:00:52 +0100 Subject: [PATCH 02/27] Small form fixes --- js/components/ascribe_forms/form.js | 2 +- js/components/ascribe_forms/form_loan.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/components/ascribe_forms/form.js b/js/components/ascribe_forms/form.js index d4002e85..d015d58d 100644 --- a/js/components/ascribe_forms/form.js +++ b/js/components/ascribe_forms/form.js @@ -156,7 +156,7 @@ let Form = React.createClass({ for(let ref in this.refs) { if(this.refs[ref] && typeof this.refs[ref].handleSuccess === 'function'){ - this.refs[ref].handleSuccess(); + this.refs[ref].handleSuccess(response); } } this.setState({ diff --git a/js/components/ascribe_forms/form_loan.js b/js/components/ascribe_forms/form_loan.js index 861806ae..e04eb209 100644 --- a/js/components/ascribe_forms/form_loan.js +++ b/js/components/ascribe_forms/form_loan.js @@ -171,7 +171,7 @@ let LoanForm = React.createClass({ editable={!gallery} overrideForm={!!gallery}> From b8d6a79402d2ba814b4350eac8ebb265317cd8cc Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 20 Jan 2016 15:11:06 +0100 Subject: [PATCH 03/27] Update README for visual regression testing --- README.md | 69 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index e07eca0d..15736654 100644 --- a/README.md +++ b/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 -===================== +============ + Since we moved to Github, we cannot create branch names automatically with JIRA anymore. To not lose context, but still be able to switch branches quickly using a ticket's number, we're recommending the following rules when naming our branches in onion. @@ -58,22 +76,21 @@ AD--brief-and-sane-description-of-the-ticket where `brief-and-sane-description-of-the-ticket` does not need to equal to the ticket's title. This allows JIRA to still track branches and pull-requests while allowing us to keep our peace of mind. + Example -------------- +------- + **JIRA ticket name:** `AD-1242 - Frontend caching for simple endpoints to measure perceived page load ` **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 `_tests.js` file needs to be created. @@ -83,7 +100,15 @@ 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 PhantomJS and SauceLabs. + + +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`. @@ -134,9 +159,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 ============ @@ -149,7 +181,6 @@ Start here - [alt.js](http://alt.js.org/) - [alt.js readme](https://github.com/goatslacker/alt) - Moar stuff ---------- From 37f5432e7514707ca4e269eca41cb31b90d08b2b Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 20 Jan 2016 21:18:24 +0100 Subject: [PATCH 04/27] Add Gemini configuration for desktop and mobile on phantoms --- .gemini.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .gemini.yml diff --git a/.gemini.yml b/.gemini.yml new file mode 100644 index 00000000..5af1ef27 --- /dev/null +++ b/.gemini.yml @@ -0,0 +1,14 @@ +rootUrl: https://www.ascribe.ninja/app + +browsers: + PhantomJSDesktop: + screenshotsDir: './gemini/screens/desktop' + windowSize: 1900x1080 + desiredCapabilities: + browserName: phantomjs + + PhantomJSMobile: + screenshotsDir: './gemini/screens/mobile' + windowSize: 767x1364 + desiredCapabilities: + browserName: phantomjs From f44597c2bfa34afb1b8d4949e9b5267150c693eb Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 25 Jan 2016 10:59:44 +0100 Subject: [PATCH 05/27] Add docs for visual regression testing Also adds a simple script for testing with phantomJS --- .gitignore | 9 ++- README.md | 4 +- docs/visual-regression-testing.md | 126 ++++++++++++++++++++++++++++++ package.json | 4 +- phantomjs/launch_app_and_login.js | 62 +++++++++++++++ 5 files changed, 200 insertions(+), 5 deletions(-) create mode 100644 docs/visual-regression-testing.md create mode 100644 phantomjs/launch_app_and_login.js diff --git a/.gitignore b/.gitignore index 30c9eae9..17b63e95 100644 --- a/.gitignore +++ b/.gitignore @@ -16,9 +16,12 @@ webapp-dependencies.txt pids logs results - + +build/* + +gemini-coverage/* +gemini-report/* + node_modules/* -build - .DS_Store diff --git a/README.md b/README.md index 15736654..015c3fe9 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,9 @@ Therefore, to require a module in your test file, you need to use CommonJS's `re Visual Regression Testing ------------------------- -We're using [Gemini](https://github.com/gemini-testing/gemini) for visual regression tests because it supports PhantomJS and SauceLabs. +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](docs/visual-regression-testing.md) for information on installing Gemini, its dependencies, and running and writing tests. Workflow diff --git a/docs/visual-regression-testing.md b/docs/visual-regression-testing.md new file mode 100644 index 00000000..30a711ff --- /dev/null +++ b/docs/visual-regression-testing.md @@ -0,0 +1,126 @@ +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](#gemini-usage) + 1. [PhantomJS](#phantomjs) + + +Installation +============ + +First install [PhantomJS2](https://www.npmjs.com/package/phantomjs2): + +```bash +npm install -g phantomjs2 + +# If using OSX, install upx and decompress the binary downloaded by npm manually: +brew install upx +# Navigate to the binary, ie. /Users/Brett/.nvm/v5.4.0/lib/node_modules/phantomjs2/lib/phantom/bin +upx -d phantomjs + +``` + +Then [install Gemini globally and locally with npm](https://github.com/gemini-testing/gemini/blob/master/README.md#installation). + + +Running Tests +============= + +Run PhantomJS: + +```bash +phantomjs --webdriver=4444 +``` + +And then run Gemini tests: + +```bash +# In root onion/ +gemini test gemini/* --report html +``` + +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 +# In root onion/ +gemini update gemini/* +``` + + +Gemini Usage +============ + +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). + +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 `--report html` flag with Gemini will produce a webpage with the test's results in /gemini-report that will show the old, new, and diff images. Using this is highly recommended (and fun!). + + +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 +``` diff --git a/package.json b/package.json index c961e9c3..d3e7ae41 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,10 @@ "devDependencies": { "babel-eslint": "^3.1.11", "babel-jest": "^5.2.0", + "gemini": "^2.0.3", "gulp-sass": "^2.1.1", - "jest-cli": "^0.4.0" + "jest-cli": "^0.4.0", + "phantomjs2": "^2.0.2" }, "dependencies": { "alt": "^0.16.5", diff --git a/phantomjs/launch_app_and_login.js b/phantomjs/launch_app_and_login.js new file mode 100644 index 00000000..e5418519 --- /dev/null +++ b/phantomjs/launch_app_and_login.js @@ -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(); + } +}); From 7edddc4d5a86867834ac3582382f9de92995df67 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 11:17:30 +0100 Subject: [PATCH 06/27] Update docs for PhantomJS 2.1, whitespace, and more tips --- docs/visual-regression-testing.md | 107 +++++++++++++++++++++++------- 1 file changed, 82 insertions(+), 25 deletions(-) diff --git a/docs/visual-regression-testing.md b/docs/visual-regression-testing.md index 30a711ff..d544b1fa 100644 --- a/docs/visual-regression-testing.md +++ b/docs/visual-regression-testing.md @@ -1,33 +1,39 @@ 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. +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](#gemini-usage) + 1. [Gemini Usage and Writing Tests](#gemini-usage-and-writing-tests) 1. [PhantomJS](#phantomjs) + 1. [TODO](#todo) Installation ============ -First install [PhantomJS2](https://www.npmjs.com/package/phantomjs2): +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 -npm install -g phantomjs2 +# 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 -# If using OSX, install upx and decompress the binary downloaded by npm manually: +# 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/v5.4.0/lib/node_modules/phantomjs2/lib/phantom/bin + +# Navigate to the binary, ie. /Users/Brett/.nvm/versions/node/v5.4.0/lib/node_modules/phantomjs2/lib/phantom/bin/phantomjs upx -d phantomjs ``` -Then [install Gemini globally and locally with npm](https://github.com/gemini-testing/gemini/blob/master/README.md#installation). +Finally, [install Gemini globally and locally with npm](https://github.com/gemini-testing/gemini/blob/master/README.md#installation). Running Tests @@ -54,41 +60,84 @@ gemini update gemini/* ``` -Gemini Usage -============ +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). +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. +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). +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. +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. +**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) +(**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 `--report html` flag with Gemini will produce a webpage with the test's results in /gemini-report that will show the old, new, and diff images. Using this is highly recommended (and fun!). +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/gemini/`. + +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. +[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. +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. +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 --------------- @@ -97,28 +146,36 @@ You can find the full list of CLI commands in the [documentation](http://phantom 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. + * `--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). +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: +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. +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. +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. +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 From 01217318a437c296001240bd0f1527ca28137f27 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 11:20:27 +0100 Subject: [PATCH 07/27] Add ids to dropdown buttons, as per warnings Also helps us find these dropdown buttons with Gemini. --- .../piece_list_toolbar_filter_widget.js | 1 + .../piece_list_toolbar_order_widget.js | 7 ++++--- js/components/header.js | 1 + js/components/header_notification.js | 1 + js/components/nav_routes_links_link.js | 6 ++++-- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js index edb29e85..c9791dbe 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js @@ -84,6 +84,7 @@ let PieceListToolbarFilterWidget = React.createClass({ if (this.props.filterParams && this.props.filterParams.length) { return ( diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js index 5257cc07..da9bae43 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js @@ -45,7 +45,7 @@ let PieceListToolbarOrderWidget = React.createClass({ }, render() { - let filterIcon = ( + let orderIcon = ( · @@ -55,9 +55,10 @@ let PieceListToolbarOrderWidget = React.createClass({ if (this.props.orderParams && this.props.orderParams.length) { return ( + className="ascribe-piece-list-toolbar-filter-widget" + title={orderIcon}>
  • {getLangText('Sort by')}:
  • diff --git a/js/components/header.js b/js/components/header.js index c16cba86..4bb6881e 100644 --- a/js/components/header.js +++ b/js/components/header.js @@ -163,6 +163,7 @@ let Header = React.createClass({ account = ( diff --git a/js/components/nav_routes_links_link.js b/js/components/nav_routes_links_link.js index 11235ccd..e2bfb7ed 100644 --- a/js/components/nav_routes_links_link.js +++ b/js/components/nav_routes_links_link.js @@ -29,7 +29,9 @@ let NavRoutesLinksLink = React.createClass({ // with MenuItems if(children) { return ( - + {children} ); @@ -55,4 +57,4 @@ let NavRoutesLinksLink = React.createClass({ } }); -export default NavRoutesLinksLink; \ No newline at end of file +export default NavRoutesLinksLink; From 7383fbb1f648214a838d0854bcd45fa984658b9d Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 11:20:43 +0100 Subject: [PATCH 08/27] Standardize CoaVerify route across apps --- js/components/coa_verify_container.js | 85 +++++++++---------- .../whitelabel/wallet/wallet_routes.js | 10 +-- 2 files changed, 46 insertions(+), 49 deletions(-) diff --git a/js/components/coa_verify_container.js b/js/components/coa_verify_container.js index 6d0af0fa..4a270cf4 100644 --- a/js/components/coa_verify_container.js +++ b/js/components/coa_verify_container.js @@ -27,7 +27,7 @@ let CoaVerifyContainer = React.createClass({ return (
    -
    +
    {getLangText('Verify your Certificate of Authenticity')}
    @@ -37,7 +37,7 @@ let CoaVerifyContainer = React.createClass({ signature={signature}/>

    - {getLangText('ascribe is using the following public key for verification')}: + {getLangText('ascribe is using the following public key for verification')}:
                     -----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 (
    -            
    -
    - {getLangText('Verify your Certificate of Authenticity')} - } - spinner={ - - - - }> - - - - - - -
    -
    -
    +
    + {getLangText('Verify your Certificate of Authenticity')} + + } + spinner={ + + + + }> + + + + + + +
    +
    ); } }); diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index a5a0e075..b4e3c7ea 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -77,7 +77,7 @@ let ROUTES = { component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(CylandPieceList)} headerTitle='COLLECTION' /> - + @@ -112,7 +112,7 @@ let ROUTES = { headerTitle='COLLECTION' /> - + ), @@ -156,7 +156,7 @@ let ROUTES = { component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(IkonotvContractNotifications)} /> - + ), @@ -192,7 +192,7 @@ let ROUTES = { headerTitle='COLLECTION' /> - + ), @@ -228,7 +228,7 @@ let ROUTES = { headerTitle='COLLECTION' /> - + ) From f9c70e2beb87af5fdcbc637cb5c65d4afd4d2398 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 11:21:57 +0100 Subject: [PATCH 09/27] Main app visual test suite --- .gemini.yml | 15 ++- gemini/main/authenticated.js | 214 +++++++++++++++++++++++++++++++++++ gemini/main/basic.js | 145 ++++++++++++++++++++++++ gemini/main/detail.js | 125 ++++++++++++++++++++ 4 files changed, 493 insertions(+), 6 deletions(-) create mode 100644 gemini/main/authenticated.js create mode 100644 gemini/main/basic.js create mode 100644 gemini/main/detail.js diff --git a/.gemini.yml b/.gemini.yml index 5af1ef27..8d434624 100644 --- a/.gemini.yml +++ b/.gemini.yml @@ -1,14 +1,17 @@ -rootUrl: https://www.ascribe.ninja/app +rootUrl: http://localhost.com:3000/ +sessionsPerBrowser: 1 browsers: - PhantomJSDesktop: - screenshotsDir: './gemini/screens/desktop' + MainDesktop: + rootUrl: http://localhost.com:3000/ + screenshotsDir: './gemini-screens/desktop' windowSize: 1900x1080 desiredCapabilities: browserName: phantomjs - PhantomJSMobile: - screenshotsDir: './gemini/screens/mobile' - windowSize: 767x1364 + MainMobile: + rootUrl: http://localhost.com:3000/ + screenshotsDir: './gemini-screens/mobile' + windowSize: 600x1056 desiredCapabilities: browserName: phantomjs diff --git a/gemini/main/authenticated.js b/gemini/main/authenticated.js new file mode 100644 index 00000000..bf579bff --- /dev/null +++ b/gemini/main/authenticated.js @@ -0,0 +1,214 @@ +'use strict'; + +const gemini = require('gemini'); + +/** + * 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', 5000); + }); + + // 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', 5000); + + actions.sendKeys(find('.ascribe-login-wrapper input[name=email]'), 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-login-wrapper input[name=password]'), '0000000000'); + actions.click(find('.ascribe-login-wrapper button[type=submit]')); + + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', 5000); + }); + }); + + 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)', 5000); + }) + .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)', 5000); + }) + .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)', 5000); + // 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(5000); + }) + .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', 5000); + }) + + 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', 5000); + }); + }); + + 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', 5000); + + 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'); + }) + + 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)', 5000); + }); + }); + + 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)', 5000); + }); + }); + }); + + 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', 5000); + }) + .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', 5000); + }) + .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', 10000); + }); + }); +}); diff --git a/gemini/main/basic.js b/gemini/main/basic.js new file mode 100644 index 00000000..317c5d84 --- /dev/null +++ b/gemini/main/basic.js @@ -0,0 +1,145 @@ +'use strict'; + +const gemini = require('gemini'); + +/** + * 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', 5000); + }); + + gemini.suite('Header-desktop', (headerSuite) => { + headerSuite + .setCaptureElements('nav.navbar .container') + .skip(/Mobile/) + .capture('desktop header', (actions, find) => { + actions.waitForElementToShow('nav.navbar .container', 5000); + }) + .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', 5000); + }) + .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', 5000); + }) + .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', 5000); + }) + .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, 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); + }) + .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', 5000); + }) + .capture('sign up form filled with focus', (actions, find) => { + actions.sendKeys(find('.ascribe-form input[name=email]'), 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); + actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), '0000000000'); + }) + .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', 5000); + }) + .capture('password reset form filled with focus', (actions, find) => { + actions.sendKeys(find('.ascribe-form input[name="email"]'), 'dimi@mailinator.com'); + }) + .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', 5000); + }) + .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'); + }); +}); diff --git a/gemini/main/detail.js b/gemini/main/detail.js new file mode 100644 index 00000000..39a02338 --- /dev/null +++ b/gemini/main/detail.js @@ -0,0 +1,125 @@ +'use strict'; + +const gemini = require('gemini'); +const pieceUrl = '/pieces/12374'; +const editionUrl = '/editions/14gw9x3VA9oJaxp4cHaAuK2bvJzvEj4Xvc'; + +/** + * 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', 5000); + + // Wait for the social media buttons to appear + actions.waitForElementToShow('.ascribe-social-button-list .fb-share-button iframe', 20000); + actions.waitForElementToShow('.ascribe-social-button-list .twitter-share-button', 20000); + actions.waitForElementToShow('.ascribe-media-player', 10000); + }); + + gemini.suite('Basic piece', (basicPieceSuite) => { + basicPieceSuite + .setUrl(pieceUrl) + .capture('basic piece') + .capture('shmui', (actions, find) => { + actions.click(find('.ascribe-media-player')); + actions.waitForElementToShow('.shmui-wrap:not(.loading)', 30000); + // 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', 5000); + }) + .capture('logged in', (actions, find) => { + actions.sendKeys(find('.ascribe-login-wrapper input[name=email]'), 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-login-wrapper input[name=password]'), '0000000000'); + actions.click(find('.ascribe-login-wrapper button[type=submit]')); + + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', 5000); + }); + }); + + 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', 5000); + }) + .capture('logout', (actions, find) => { + actions.waitForElementToShow('.ascribe-login-wrapper', 10000); + }); + }); +}); From cf0a66ef2e792e4ffaee42d06467895f76275f60 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 11:24:01 +0100 Subject: [PATCH 10/27] Add whitelabel visual test suite Currently just checks landing page and some pages accessible to unauthorized users. --- .gemini.yml | 116 +++++++++++++++++++ gemini/whitelabel/23vivi/23vivi.js | 27 +++++ gemini/whitelabel/cyland/cyland.js | 28 +++++ gemini/whitelabel/ikonotv/ikonotv.js | 95 +++++++++++++++ gemini/whitelabel/lumenus/lumenus.js | 27 +++++ gemini/whitelabel/shared/whitelabel_basic.js | 112 ++++++++++++++++++ 6 files changed, 405 insertions(+) create mode 100644 gemini/whitelabel/23vivi/23vivi.js create mode 100644 gemini/whitelabel/cyland/cyland.js create mode 100644 gemini/whitelabel/ikonotv/ikonotv.js create mode 100644 gemini/whitelabel/lumenus/lumenus.js create mode 100644 gemini/whitelabel/shared/whitelabel_basic.js diff --git a/.gemini.yml b/.gemini.yml index 8d434624..a04903eb 100644 --- a/.gemini.yml +++ b/.gemini.yml @@ -15,3 +15,119 @@ browsers: windowSize: 600x1056 desiredCapabilities: browserName: phantomjs + + CcDesktop: + rootUrl: http://cc.localhost.com:3000/ + screenshotsDir: './gemini-screens/cc-desktop' + windowSize: 1900x1080 + desiredCapabilities: + browserName: phantomjs + + CcMobile: + rootUrl: http://cc.localhost.com:3000/ + screenshotsDir: './gemini-screens/cc-mobile' + windowSize: 600x1056 + desiredCapabilities: + browserName: phantomjs + + CylandDesktop: + rootUrl: http://cyland.localhost.com:3000/ + screenshotsDir: './gemini-screens/cyland-desktop' + windowSize: 1900x1080 + desiredCapabilities: + browserName: phantomjs + + CylandMobile: + rootUrl: http://cyland.localhost.com:3000/ + screenshotsDir: './gemini-screens/cyland-mobile' + windowSize: 600x1056 + desiredCapabilities: + browserName: phantomjs + + IkonotvDesktop: + rootUrl: http://ikonotv.localhost.com:3000/ + screenshotsDir: './gemini-screens/ikonotv-desktop' + windowSize: 1900x1080 + desiredCapabilities: + browserName: phantomjs + + IkonotvMobile: + rootUrl: http://ikonotv.localhost.com:3000/ + screenshotsDir: './gemini-screens/ikonotv-mobile' + windowSize: 600x1056 + desiredCapabilities: + browserName: phantomjs + + LumenusDesktop: + rootUrl: http://lumenus.localhost.com:3000/ + screenshotsDir: './gemini-screens/lumenus-desktop' + windowSize: 1900x1080 + desiredCapabilities: + browserName: phantomjs + + LumenusMobile: + rootUrl: http://lumenus.localhost.com:3000/ + screenshotsDir: './gemini-screens/lumenus-mobile' + windowSize: 600x1056 + desiredCapabilities: + browserName: phantomjs + + 23viviDesktop: + rootUrl: http://23vivi.localhost.com:3000/ + screenshotsDir: './gemini-screens/23vivi-desktop' + windowSize: 1900x1080 + desiredCapabilities: + browserName: phantomjs + + 23viviMobile: + rootUrl: http://23vivi.localhost.com:3000/ + screenshotsDir: './gemini-screens/23vivi-mobile' + windowSize: 600x1056 + desiredCapabilities: + browserName: phantomjs + +sets: + main: + files: + - gemini/main + browsers: + - MainDesktop + - MainMobile + cc: + files: + - gemini/whitelabel/shared + browsers: + - CcDesktop + - CcMobile + + cyland: + files: + - gemini/whitelabel/shared + - gemini/whitelabel/cyland + browsers: + - CylandDesktop + - CylandMobile + + ikonotv: + files: + - gemini/whitelabel/shared + - gemini/whitelabel/ikonotv + browsers: + - IkonotvDesktop + - IkonotvMobile + + lumenus: + files: + - gemini/whitelabel/shared + - gemini/whitelabel/lumenus + browsers: + - LumenusDesktop + - LumenusMobile + + 23vivi: + files: + - gemini/whitelabel/shared + - gemini/whitelabel/23vivi + browsers: + - 23viviDesktop + - 23viviMobile diff --git a/gemini/whitelabel/23vivi/23vivi.js b/gemini/whitelabel/23vivi/23vivi.js new file mode 100644 index 00000000..cafdfc6d --- /dev/null +++ b/gemini/whitelabel/23vivi/23vivi.js @@ -0,0 +1,27 @@ +'use strict'; + +const gemini = require('gemini'); + +/** + * 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', 5000); + }); + + gemini.suite('Landing', (landingSuite) => { + landingSuite + .setUrl('/') + .capture('landing', (actions, find) => { + // Wait for the logo to appear + actions.waitForElementToShow('.vivi23-landing--header-logo', 10000); + }); + }); + + // TODO: add more tests for market specific pages after authentication +}); diff --git a/gemini/whitelabel/cyland/cyland.js b/gemini/whitelabel/cyland/cyland.js new file mode 100644 index 00000000..06709f39 --- /dev/null +++ b/gemini/whitelabel/cyland/cyland.js @@ -0,0 +1,28 @@ +'use strict'; + +const gemini = require('gemini'); + +/** + * 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', 5000); + }); + + 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', 10000); + }); + }); + + // TODO: add more tests for cyland specific pages after authentication +}); diff --git a/gemini/whitelabel/ikonotv/ikonotv.js b/gemini/whitelabel/ikonotv/ikonotv.js new file mode 100644 index 00000000..1741aaa0 --- /dev/null +++ b/gemini/whitelabel/ikonotv/ikonotv.js @@ -0,0 +1,95 @@ +'use strict'; + +const gemini = require('gemini'); + +/** + * 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', 5000); + }); + + 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', 10000); + }); + }); + + // 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', 5000); + + // Wait for the forms to appear + actions.waitForElementToShow('.ascribe-form', 5000); + + // Just use a dumb wait because the logo is set as a background image + actions.wait(3000); + }); + + 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, 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); + }) + .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]'), 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); + actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), '0000000000'); + }) + .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 +}); diff --git a/gemini/whitelabel/lumenus/lumenus.js b/gemini/whitelabel/lumenus/lumenus.js new file mode 100644 index 00000000..a9ff53cd --- /dev/null +++ b/gemini/whitelabel/lumenus/lumenus.js @@ -0,0 +1,27 @@ +'use strict'; + +const gemini = require('gemini'); + +/** + * 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', 5000); + }); + + gemini.suite('Landing', (landingSuite) => { + landingSuite + .setUrl('/') + .capture('landing', (actions, find) => { + // Wait for the logo to appear + actions.waitForElementToShow('.wp-landing-wrapper img', 10000); + }); + }); + + // TODO: add more tests for market specific pages after authentication +}); diff --git a/gemini/whitelabel/shared/whitelabel_basic.js b/gemini/whitelabel/shared/whitelabel_basic.js new file mode 100644 index 00000000..7fe5c256 --- /dev/null +++ b/gemini/whitelabel/shared/whitelabel_basic.js @@ -0,0 +1,112 @@ +'use strict'; + +const gemini = require('gemini'); + +/** + * 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', 5000); + + // 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', 5000); + // 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(3000); + }) + .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, 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); + }) + .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', 5000); + // 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]'), 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); + actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), '0000000000'); + }) + .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', 5000); + // 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"]'), 'dimi@mailinator.com'); + }) + .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', 5000); + // 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'); + }); +}); From 331fdb528de81d2a3378502473f6e3793b035cb7 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 11:24:28 +0100 Subject: [PATCH 11/27] Remove browser-sync notify pop over that shows up in visual tests --- gulpfile.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index f13945b0..afa0d5a9 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -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 }); }); From 83b5b09b874155522b45734581c50b32fe3da6c9 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 11:26:10 +0100 Subject: [PATCH 12/27] Add npm scripts for running visual tests --- docs/visual-regression-testing.md | 33 +++++++++++++++++++++++++------ package.json | 17 ++++++++++++++-- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/docs/visual-regression-testing.md b/docs/visual-regression-testing.md index d544b1fa..35d328f0 100644 --- a/docs/visual-regression-testing.md +++ b/docs/visual-regression-testing.md @@ -42,21 +42,32 @@ Running Tests Run PhantomJS: ```bash -phantomjs --webdriver=4444 +npm run vi-phantom ``` And then run Gemini tests: ```bash -# In root onion/ -gemini test gemini/* --report html +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 +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 -# In root onion/ -gemini update gemini/* +npm run vi-update + +# Update just the main app for desktop and mobile +npm run vi-update -- --browser MainDesktop --browser MainMobile ``` @@ -112,6 +123,8 @@ are available. Our tests are located in `onion/gemini/`. +**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 @@ -181,3 +194,11 @@ change the environment to run against. # 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 diff --git a/package.json b/package.json index d3e7ae41..dd8b3244 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,20 @@ "lint": "eslint ./js", "postinstall": "npm run build", "build": "gulp build --production", - "start": "node server.js" + "start": "node server.js", + + "vi-clean": "rm -rf gemini-report", + "vi-phantom": "phantomjs --webdriver=4444", + "vi-update": "gemini update", + "vi-test": "npm run vi-clean && gemini test --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" @@ -35,7 +48,7 @@ "devDependencies": { "babel-eslint": "^3.1.11", "babel-jest": "^5.2.0", - "gemini": "^2.0.3", + "gemini": "^2.1.0", "gulp-sass": "^2.1.1", "jest-cli": "^0.4.0", "phantomjs2": "^2.0.2" From cc072199a12fca547d5ed295ab44d8e764e0061f Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 11:48:37 +0100 Subject: [PATCH 13/27] Update npm scripts for sauce labs --- package.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index ba9ba324..bfe0c416 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,9 @@ "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", + "tunnel": "node test/tunnel.js", "vi-clean": "rm -rf gemini-report", "vi-phantom": "phantomjs --webdriver=4444", "vi-update": "gemini update", From 8cb953618729eaf73315045b866bf75c7939b01b Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 13:39:46 +0100 Subject: [PATCH 14/27] Move sauce tests to /test/integration folder --- package.json | 4 ++-- test/{ => integration}/README.md | 0 test/{ => integration}/config.js | 0 test/{ => integration}/setup.js | 0 test/{ => integration/tests}/test-login.js | 2 +- test/{ => integration}/tunnel.js | 0 6 files changed, 3 insertions(+), 3 deletions(-) rename test/{ => integration}/README.md (100%) rename test/{ => integration}/config.js (100%) rename test/{ => integration}/setup.js (100%) rename test/{ => integration/tests}/test-login.js (97%) rename test/{ => integration}/tunnel.js (100%) diff --git a/package.json b/package.json index bfe0c416..42c8bba9 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "build": "gulp build --production", "start": "node server.js", "test": "npm run sauce-test", - "sauce-test": "mocha", - "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", diff --git a/test/README.md b/test/integration/README.md similarity index 100% rename from test/README.md rename to test/integration/README.md diff --git a/test/config.js b/test/integration/config.js similarity index 100% rename from test/config.js rename to test/integration/config.js diff --git a/test/setup.js b/test/integration/setup.js similarity index 100% rename from test/setup.js rename to test/integration/setup.js diff --git a/test/test-login.js b/test/integration/tests/test-login.js similarity index 97% rename from test/test-login.js rename to test/integration/tests/test-login.js index e2736fe1..853d48e5 100644 --- a/test/test-login.js +++ b/test/integration/tests/test-login.js @@ -5,7 +5,7 @@ const wd = require('wd'); const asserters = wd.asserters; // Commonly used asserters for async waits in the browser const chai = require('chai'); const chaiAsPromised = require('chai-as-promised'); -const config = require('./config.js'); +const config = require('../config.js'); chai.use(chaiAsPromised); chai.should(); diff --git a/test/tunnel.js b/test/integration/tunnel.js similarity index 100% rename from test/tunnel.js rename to test/integration/tunnel.js From 27ebf9d71138733f8472c8f0bde171168aae538b Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 13:58:52 +0100 Subject: [PATCH 15/27] Move visual tests to /test/gemini folder --- package.json | 6 +++--- .gemini.yml => test/gemini/.gemini.yml | 20 +++++++++---------- .../gemini/README.md | 3 ++- .../gemini/tests}/main/authenticated.js | 0 {gemini => test/gemini/tests}/main/basic.js | 0 {gemini => test/gemini/tests}/main/detail.js | 0 .../gemini/tests}/whitelabel/23vivi/23vivi.js | 0 .../gemini/tests}/whitelabel/cyland/cyland.js | 0 .../tests}/whitelabel/ikonotv/ikonotv.js | 0 .../tests}/whitelabel/lumenus/lumenus.js | 0 .../whitelabel/shared/whitelabel_basic.js | 0 11 files changed, 15 insertions(+), 14 deletions(-) rename .gemini.yml => test/gemini/.gemini.yml (89%) rename docs/visual-regression-testing.md => test/gemini/README.md (97%) rename {gemini => test/gemini/tests}/main/authenticated.js (100%) rename {gemini => test/gemini/tests}/main/basic.js (100%) rename {gemini => test/gemini/tests}/main/detail.js (100%) rename {gemini => test/gemini/tests}/whitelabel/23vivi/23vivi.js (100%) rename {gemini => test/gemini/tests}/whitelabel/cyland/cyland.js (100%) rename {gemini => test/gemini/tests}/whitelabel/ikonotv/ikonotv.js (100%) rename {gemini => test/gemini/tests}/whitelabel/lumenus/lumenus.js (100%) rename {gemini => test/gemini/tests}/whitelabel/shared/whitelabel_basic.js (100%) diff --git a/package.json b/package.json index 42c8bba9..5e72b863 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,10 @@ "test": "npm run sauce-test", "sauce-test": "mocha ./test/integration/tests/", "tunnel": "node ./test/integration/tunnel.js", - "vi-clean": "rm -rf gemini-report", + "vi-clean": "rm -rf ./gemini-report", "vi-phantom": "phantomjs --webdriver=4444", - "vi-update": "gemini update", - "vi-test": "npm run vi-clean && gemini test --reporter html --reporter vflat || true", + "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", diff --git a/.gemini.yml b/test/gemini/.gemini.yml similarity index 89% rename from .gemini.yml rename to test/gemini/.gemini.yml index a04903eb..f64d865f 100644 --- a/.gemini.yml +++ b/test/gemini/.gemini.yml @@ -89,45 +89,45 @@ browsers: sets: main: files: - - gemini/main + - tests/main browsers: - MainDesktop - MainMobile cc: files: - - gemini/whitelabel/shared + - tests/whitelabel/shared browsers: - CcDesktop - CcMobile cyland: files: - - gemini/whitelabel/shared - - gemini/whitelabel/cyland + - tests/whitelabel/shared + - tests/whitelabel/cyland browsers: - CylandDesktop - CylandMobile ikonotv: files: - - gemini/whitelabel/shared - - gemini/whitelabel/ikonotv + - tests/whitelabel/shared + - tests/whitelabel/ikonotv browsers: - IkonotvDesktop - IkonotvMobile lumenus: files: - - gemini/whitelabel/shared - - gemini/whitelabel/lumenus + - tests/whitelabel/shared + - tests/whitelabel/lumenus browsers: - LumenusDesktop - LumenusMobile 23vivi: files: - - gemini/whitelabel/shared - - gemini/whitelabel/23vivi + - tests/whitelabel/shared + - tests/whitelabel/23vivi browsers: - 23viviDesktop - 23viviMobile diff --git a/docs/visual-regression-testing.md b/test/gemini/README.md similarity index 97% rename from docs/visual-regression-testing.md rename to test/gemini/README.md index 35d328f0..da1712d9 100644 --- a/docs/visual-regression-testing.md +++ b/test/gemini/README.md @@ -24,6 +24,7 @@ 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 @@ -121,7 +122,7 @@ See [the docs](https://github.com/gemini-testing/gemini/blob/master/doc/tests.md 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/gemini/`. +Our tests are located in `onion/test/gemini/tests/`. **It would be nice if we kept the whitelabels up to date.** diff --git a/gemini/main/authenticated.js b/test/gemini/tests/main/authenticated.js similarity index 100% rename from gemini/main/authenticated.js rename to test/gemini/tests/main/authenticated.js diff --git a/gemini/main/basic.js b/test/gemini/tests/main/basic.js similarity index 100% rename from gemini/main/basic.js rename to test/gemini/tests/main/basic.js diff --git a/gemini/main/detail.js b/test/gemini/tests/main/detail.js similarity index 100% rename from gemini/main/detail.js rename to test/gemini/tests/main/detail.js diff --git a/gemini/whitelabel/23vivi/23vivi.js b/test/gemini/tests/whitelabel/23vivi/23vivi.js similarity index 100% rename from gemini/whitelabel/23vivi/23vivi.js rename to test/gemini/tests/whitelabel/23vivi/23vivi.js diff --git a/gemini/whitelabel/cyland/cyland.js b/test/gemini/tests/whitelabel/cyland/cyland.js similarity index 100% rename from gemini/whitelabel/cyland/cyland.js rename to test/gemini/tests/whitelabel/cyland/cyland.js diff --git a/gemini/whitelabel/ikonotv/ikonotv.js b/test/gemini/tests/whitelabel/ikonotv/ikonotv.js similarity index 100% rename from gemini/whitelabel/ikonotv/ikonotv.js rename to test/gemini/tests/whitelabel/ikonotv/ikonotv.js diff --git a/gemini/whitelabel/lumenus/lumenus.js b/test/gemini/tests/whitelabel/lumenus/lumenus.js similarity index 100% rename from gemini/whitelabel/lumenus/lumenus.js rename to test/gemini/tests/whitelabel/lumenus/lumenus.js diff --git a/gemini/whitelabel/shared/whitelabel_basic.js b/test/gemini/tests/whitelabel/shared/whitelabel_basic.js similarity index 100% rename from gemini/whitelabel/shared/whitelabel_basic.js rename to test/gemini/tests/whitelabel/shared/whitelabel_basic.js From 29b1d8f7e5267cdb22a880a94f0f621e4eb1356e Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 13:58:57 +0100 Subject: [PATCH 16/27] Update README for updated paths to tests --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b918e5d..8dcf78ad 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,14 @@ 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](docs/visual-regression-testing.md) for information on installing Gemini, its dependencies, and running and writing tests. +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 From 11f644dd117ec5ed550d7740daa5e8d7bae422b7 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 14:37:41 +0100 Subject: [PATCH 17/27] Fix shmui visual test --- test/gemini/tests/main/detail.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/test/gemini/tests/main/detail.js b/test/gemini/tests/main/detail.js index 39a02338..7adad5d4 100644 --- a/test/gemini/tests/main/detail.js +++ b/test/gemini/tests/main/detail.js @@ -29,12 +29,17 @@ gemini.suite('Work detail', (suite) => { basicPieceSuite .setUrl(pieceUrl) .capture('basic piece') - .capture('shmui', (actions, find) => { - actions.click(find('.ascribe-media-player')); - actions.waitForElementToShow('.shmui-wrap:not(.loading)', 30000); - // Wait for the transition to end - actions.wait(1000); - }); + + gemini.suite('Shmui', (shmuiSuite) => { + shmuiSuite. + setCaptureElements('.shmui-wrap') + .capture('shmui', (actions, find) => { + actions.click(find('.ascribe-media-player')); + actions.waitForElementToShow('.shmui-wrap:not(.loading)', 30000); + // Wait for the transition to end + actions.wait(1000); + }); + }); }); gemini.suite('Basic edition', (basicEditionSuite) => { From a785fbc22047ec1a81cae6228638791837df5fb1 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 14:37:55 +0100 Subject: [PATCH 18/27] Change screenshot folder for visual tests --- test/gemini/.gemini.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/gemini/.gemini.yml b/test/gemini/.gemini.yml index f64d865f..0c7b8b77 100644 --- a/test/gemini/.gemini.yml +++ b/test/gemini/.gemini.yml @@ -4,84 +4,84 @@ sessionsPerBrowser: 1 browsers: MainDesktop: rootUrl: http://localhost.com:3000/ - screenshotsDir: './gemini-screens/desktop' + screenshotsDir: './screenshots/main-desktop' windowSize: 1900x1080 desiredCapabilities: browserName: phantomjs MainMobile: rootUrl: http://localhost.com:3000/ - screenshotsDir: './gemini-screens/mobile' + screenshotsDir: './screenshots/main-mobile' windowSize: 600x1056 desiredCapabilities: browserName: phantomjs CcDesktop: rootUrl: http://cc.localhost.com:3000/ - screenshotsDir: './gemini-screens/cc-desktop' + screenshotsDir: './screenshots/cc-desktop' windowSize: 1900x1080 desiredCapabilities: browserName: phantomjs CcMobile: rootUrl: http://cc.localhost.com:3000/ - screenshotsDir: './gemini-screens/cc-mobile' + screenshotsDir: './screenshots/cc-mobile' windowSize: 600x1056 desiredCapabilities: browserName: phantomjs CylandDesktop: rootUrl: http://cyland.localhost.com:3000/ - screenshotsDir: './gemini-screens/cyland-desktop' + screenshotsDir: './screenshots/cyland-desktop' windowSize: 1900x1080 desiredCapabilities: browserName: phantomjs CylandMobile: rootUrl: http://cyland.localhost.com:3000/ - screenshotsDir: './gemini-screens/cyland-mobile' + screenshotsDir: './screenshots/cyland-mobile' windowSize: 600x1056 desiredCapabilities: browserName: phantomjs IkonotvDesktop: rootUrl: http://ikonotv.localhost.com:3000/ - screenshotsDir: './gemini-screens/ikonotv-desktop' + screenshotsDir: './screenshots/ikonotv-desktop' windowSize: 1900x1080 desiredCapabilities: browserName: phantomjs IkonotvMobile: rootUrl: http://ikonotv.localhost.com:3000/ - screenshotsDir: './gemini-screens/ikonotv-mobile' + screenshotsDir: './screenshots/ikonotv-mobile' windowSize: 600x1056 desiredCapabilities: browserName: phantomjs LumenusDesktop: rootUrl: http://lumenus.localhost.com:3000/ - screenshotsDir: './gemini-screens/lumenus-desktop' + screenshotsDir: './screenshots/lumenus-desktop' windowSize: 1900x1080 desiredCapabilities: browserName: phantomjs LumenusMobile: rootUrl: http://lumenus.localhost.com:3000/ - screenshotsDir: './gemini-screens/lumenus-mobile' + screenshotsDir: './screenshots/lumenus-mobile' windowSize: 600x1056 desiredCapabilities: browserName: phantomjs 23viviDesktop: rootUrl: http://23vivi.localhost.com:3000/ - screenshotsDir: './gemini-screens/23vivi-desktop' + screenshotsDir: './screenshots/23vivi-desktop' windowSize: 1900x1080 desiredCapabilities: browserName: phantomjs 23viviMobile: rootUrl: http://23vivi.localhost.com:3000/ - screenshotsDir: './gemini-screens/23vivi-mobile' + screenshotsDir: './screenshots/23vivi-mobile' windowSize: 600x1056 desiredCapabilities: browserName: phantomjs From e2a481a3f0a35c94e7f3e661e13e24e3bbde2909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Mon, 1 Feb 2016 13:50:55 +0100 Subject: [PATCH 19/27] Fix redirection of wallet owners This also fixes a problem where newly created users would be redirected to a 404 page. --- js/components/piece_list.js | 20 ++++++++--- .../components/prize_piece_list.js | 9 +++-- .../components/23vivi/23vivi_piece_list.js | 2 +- .../components/cyland/cyland_piece_list.js | 33 +++++++++++++++-- .../components/ikonotv/ikonotv_piece_list.js | 35 +++++++++++++++---- .../components/market/market_piece_list.js | 15 ++++++-- .../market/market_register_piece.js | 2 +- 7 files changed, 95 insertions(+), 21 deletions(-) diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 567a53e9..82fc2df3 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -36,7 +36,10 @@ let PieceList = React.createClass({ accordionListItemType: React.PropTypes.func, bulkModalButtonListType: React.PropTypes.func, canLoadPieceList: React.PropTypes.bool, - redirectTo: React.PropTypes.string, + redirectTo: React.PropTypes.shape({ + pathname: React.PropTypes.string, + query: React.PropTypes.object + }), shouldRedirect: React.PropTypes.func, customSubmitButton: React.PropTypes.element, customThumbnailPlaceholder: React.PropTypes.func, @@ -62,7 +65,10 @@ let PieceList = React.createClass({ ] }], orderParams: ['artist_name', 'title'], - redirectTo: '/register_piece', + redirectTo: { + pathname: '/register_piece', + query: {} + }, shouldRedirect: () => true }; }, @@ -120,10 +126,16 @@ let PieceList = React.createClass({ const { location: { query }, redirectTo, shouldRedirect } = this.props; const { unfilteredPieceListCount } = this.state; - if (redirectTo && unfilteredPieceListCount === 0 && + if (redirectTo && redirectTo.pathname && unfilteredPieceListCount === 0 && (typeof shouldRedirect === 'function' && shouldRedirect(unfilteredPieceListCount))) { // FIXME: hack to redirect out of the dispatch cycle - window.setTimeout(() => this.history.push({ query, pathname: redirectTo }), 0); + window.setTimeout(() => this.history.push({ + // Occasionally, the back end also sets query parameters for Onion. + // We need to consider this by merging all passed query parameters, as we'll + // otherwise end up in a 404 screen + query: Object.assign(query, redirectTo.query), + pathname: redirectTo.pathname + }), 0); } }, diff --git a/js/components/whitelabel/prize/simple_prize/components/prize_piece_list.js b/js/components/whitelabel/prize/simple_prize/components/prize_piece_list.js index bd2b9f06..ec0def22 100644 --- a/js/components/whitelabel/prize/simple_prize/components/prize_piece_list.js +++ b/js/components/whitelabel/prize/simple_prize/components/prize_piece_list.js @@ -62,13 +62,15 @@ let PrizePieceList = React.createClass({ }, render() { + const { is_judge: isJudge, is_jury: isJury, is_admin: isAdmin } = this.state.currentUser; + setDocumentTitle(getLangText('Collection')); let orderParams = ['artist_name', 'title']; - if (this.state.currentUser.is_jury) { + if (isJury) { orderParams = ['rating', 'title']; } - if (this.state.currentUser.is_judge) { + if (isJudge) { orderParams = ['rating', 'title', 'selected']; } return ( @@ -80,7 +82,8 @@ let PrizePieceList = React.createClass({ orderBy={this.state.currentUser.is_jury ? 'rating' : null} filterParams={[]} customSubmitButton={this.getButtonSubmit()} - location={this.props.location}/> + location={this.props.location} + shouldRedirect={() => !(isJury || isJudge || isAdmin)} />
    ); } diff --git a/js/components/whitelabel/wallet/components/23vivi/23vivi_piece_list.js b/js/components/whitelabel/wallet/components/23vivi/23vivi_piece_list.js index 0bfb8aa0..fa2bf5f0 100644 --- a/js/components/whitelabel/wallet/components/23vivi/23vivi_piece_list.js +++ b/js/components/whitelabel/wallet/components/23vivi/23vivi_piece_list.js @@ -1,4 +1,4 @@ -'use strict' +'use strict'; import React from 'react'; diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_piece_list.js b/js/components/whitelabel/wallet/components/cyland/cyland_piece_list.js index c40b455a..288b7eb8 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_piece_list.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_piece_list.js @@ -6,11 +6,14 @@ import PieceList from '../../../../piece_list'; import UserActions from '../../../../../actions/user_actions'; import UserStore from '../../../../../stores/user_store'; +import WhitelabelActions from '../../../../../actions/whitelabel_actions'; +import WhitelabelStore from '../../../../../stores/whitelabel_store'; + import CylandAccordionListItem from './cyland_accordion_list/cyland_accordion_list_item'; import { getLangText } from '../../../../../utils/lang_utils'; import { setDocumentTitle } from '../../../../../utils/dom_utils'; - +import { mergeOptions } from '../../../../../utils/general_utils'; let CylandPieceList = React.createClass({ propTypes: { @@ -18,15 +21,22 @@ let CylandPieceList = React.createClass({ }, getInitialState() { - return UserStore.getState(); + return mergeOptions( + UserStore.getState(), + WhitelabelStore.getState() + ); }, componentDidMount() { UserStore.listen(this.onChange); + WhitelabelStore.listen(this.onChange); + + WhitelabelActions.fetchWhitelabel(); UserActions.fetchCurrentUser(); }, componentWillUnmount() { + WhitelabelStore.unlisten(this.onChange); UserStore.unlisten(this.onChange); }, @@ -34,13 +44,30 @@ let CylandPieceList = React.createClass({ this.setState(state); }, + shouldRedirect(pieceCount) { + const { + currentUser: { email: userEmail }, + whitelabel: { + user: whitelabelAdminEmail + } + } = this.state; + + return userEmail !== whitelabelAdminEmail && !pieceCount; + }, + render() { setDocumentTitle(getLangText('Collection')); return (
    !isUserAdmin && !pieceCount; + }, + render() { const { customThumbnailPlaceholder, location } = this.props; const { @@ -59,11 +63,12 @@ let MarketPieceList = React.createClass({ } } = this.state; let filterParams = null; + let isUserAdmin = null; let canLoadPieceList = false; if (userEmail && whitelabelAdminEmail) { canLoadPieceList = true; - const isUserAdmin = userEmail === whitelabelAdminEmail; + isUserAdmin = userEmail === whitelabelAdminEmail; filterParams = [{ label: getLangText('Show works I can'), @@ -78,7 +83,13 @@ let MarketPieceList = React.createClass({ return ( Date: Mon, 1 Feb 2016 14:47:51 +0100 Subject: [PATCH 20/27] Fix PR feedback --- .../accordion_list_item_thumbnail_placeholder.js | 2 +- js/components/piece_list.js | 8 ++++---- .../sluice_selected_prize_action_button.js | 2 +- ...3vivi_accordion_list_item_thumbnail_placeholder.js | 2 +- .../wallet/components/cyland/cyland_piece_list.js | 6 ++---- .../wallet/components/ikonotv/ikonotv_piece_list.js | 8 +++----- .../wallet/components/market/market_piece_list.js | 6 +----- .../wallet/components/market/market_register_piece.js | 11 +++++------ js/utils/regex_utils.js | 2 +- js/utils/url_utils.js | 2 +- 10 files changed, 20 insertions(+), 29 deletions(-) diff --git a/js/components/ascribe_accordion_list/accordion_list_item_thumbnail_placeholder.js b/js/components/ascribe_accordion_list/accordion_list_item_thumbnail_placeholder.js index 37c98371..8000affd 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_thumbnail_placeholder.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_thumbnail_placeholder.js @@ -1,4 +1,4 @@ -'use strict' +'use strict'; import React from 'react'; diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 82fc2df3..caf48aae 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -67,9 +67,9 @@ let PieceList = React.createClass({ orderParams: ['artist_name', 'title'], redirectTo: { pathname: '/register_piece', - query: {} + query: null }, - shouldRedirect: () => true + shouldRedirect: (pieceCount) => !pieceCount }; }, @@ -126,14 +126,14 @@ let PieceList = React.createClass({ const { location: { query }, redirectTo, shouldRedirect } = this.props; const { unfilteredPieceListCount } = this.state; - if (redirectTo && redirectTo.pathname && unfilteredPieceListCount === 0 && + if (redirectTo && redirectTo.pathname && (typeof shouldRedirect === 'function' && shouldRedirect(unfilteredPieceListCount))) { // FIXME: hack to redirect out of the dispatch cycle window.setTimeout(() => this.history.push({ // Occasionally, the back end also sets query parameters for Onion. // We need to consider this by merging all passed query parameters, as we'll // otherwise end up in a 404 screen - query: Object.assign(query, redirectTo.query), + query: Object.assign({}, query, redirectTo.query), pathname: redirectTo.pathname }), 0); } diff --git a/js/components/whitelabel/prize/sluice/components/sluice_buttons/sluice_selected_prize_action_button.js b/js/components/whitelabel/prize/sluice/components/sluice_buttons/sluice_selected_prize_action_button.js index 7778a8de..7d013d5d 100644 --- a/js/components/whitelabel/prize/sluice/components/sluice_buttons/sluice_selected_prize_action_button.js +++ b/js/components/whitelabel/prize/sluice/components/sluice_buttons/sluice_selected_prize_action_button.js @@ -1,4 +1,4 @@ -'use strict' +'use strict'; import React from 'react'; import Moment from 'moment'; diff --git a/js/components/whitelabel/wallet/components/23vivi/23vivi_accordion_list/23vivi_accordion_list_item_thumbnail_placeholder.js b/js/components/whitelabel/wallet/components/23vivi/23vivi_accordion_list/23vivi_accordion_list_item_thumbnail_placeholder.js index f360c932..3a9b3943 100644 --- a/js/components/whitelabel/wallet/components/23vivi/23vivi_accordion_list/23vivi_accordion_list_item_thumbnail_placeholder.js +++ b/js/components/whitelabel/wallet/components/23vivi/23vivi_accordion_list/23vivi_accordion_list_item_thumbnail_placeholder.js @@ -1,4 +1,4 @@ -'use strict' +'use strict'; import React from 'react'; diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_piece_list.js b/js/components/whitelabel/wallet/components/cyland/cyland_piece_list.js index 288b7eb8..29c7d46f 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_piece_list.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_piece_list.js @@ -45,12 +45,10 @@ let CylandPieceList = React.createClass({ }, shouldRedirect(pieceCount) { - const { - currentUser: { email: userEmail }, + const { currentUser: { email: userEmail }, whitelabel: { user: whitelabelAdminEmail - } - } = this.state; + } } = this.state; return userEmail !== whitelabelAdminEmail && !pieceCount; }, diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_piece_list.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_piece_list.js index 1189992b..eccfd89c 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_piece_list.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_piece_list.js @@ -52,13 +52,11 @@ let IkonotvPieceList = React.createClass({ }, shouldRedirect(pieceCount) { - const { - contractAgreementListNotifications, - currentUser: { email: userEmail }, + const { contractAgreementListNotifications, + currentUser: { email: userEmail }, whitelabel: { user: whitelabelAdminEmail - } - } = this.state; + } } = this.state; return contractAgreementListNotifications && !contractAgreementListNotifications.length && diff --git a/js/components/whitelabel/wallet/components/market/market_piece_list.js b/js/components/whitelabel/wallet/components/market/market_piece_list.js index ce8ca42b..ab9b31b2 100644 --- a/js/components/whitelabel/wallet/components/market/market_piece_list.js +++ b/js/components/whitelabel/wallet/components/market/market_piece_list.js @@ -49,10 +49,6 @@ let MarketPieceList = React.createClass({ this.setState(state); }, - shouldRedirect(isUserAdmin) { - return (pieceCount) => !isUserAdmin && !pieceCount; - }, - render() { const { customThumbnailPlaceholder, location } = this.props; const { @@ -89,7 +85,7 @@ let MarketPieceList = React.createClass({ 'slide_num': 0 } }} - shouldRedirect={this.shouldRedirect(isUserAdmin)} + shouldRedirect={(pieceCount) => !isUserAdmin && !pieceCount} bulkModalButtonListType={MarketAclButtonList} customThumbnailPlaceholder={customThumbnailPlaceholder} filterParams={filterParams} diff --git a/js/components/whitelabel/wallet/components/market/market_register_piece.js b/js/components/whitelabel/wallet/components/market/market_register_piece.js index c227722b..aa71c207 100644 --- a/js/components/whitelabel/wallet/components/market/market_register_piece.js +++ b/js/components/whitelabel/wallet/components/market/market_register_piece.js @@ -112,12 +112,11 @@ let MarketRegisterPiece = React.createClass({ render() { const { location } = this.props; - const { - piece, - step, - whitelabel: { - name: whitelabelName = 'Market' - } } = this.state; + const { piece, + step, + whitelabel: { + name: whitelabelName = 'Market' + } } = this.state; setDocumentTitle(getLangText('Register a new piece')); diff --git a/js/utils/regex_utils.js b/js/utils/regex_utils.js index 49412d07..13c14ecd 100644 --- a/js/utils/regex_utils.js +++ b/js/utils/regex_utils.js @@ -1,4 +1,4 @@ -'use strict' +'use strict'; // TODO: Create Unittests that test all functions diff --git a/js/utils/url_utils.js b/js/utils/url_utils.js index 94584026..41306498 100644 --- a/js/utils/url_utils.js +++ b/js/utils/url_utils.js @@ -1,4 +1,4 @@ -'use strict' +'use strict'; import camelCase from 'camelcase'; import decamelize from 'decamelize'; From f5a341b37e054de3b4ab3096c8a7630d4ba9ccad Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 1 Feb 2016 17:12:49 +0100 Subject: [PATCH 21/27] Add environment config file for visual regression tests Wayyyyy better than hard coding diminator everywhere. --- phantomjs/launch_app_and_login.js | 62 ---------------- test/gemini/README.md | 9 ++- test/gemini/tests/environment.js | 22 ++++++ test/gemini/tests/main/authenticated.js | 5 +- test/gemini/tests/main/basic.js | 13 ++-- test/gemini/tests/main/detail.js | 9 +-- .../tests/whitelabel/ikonotv/ikonotv.js | 11 +-- .../whitelabel/shared/whitelabel_basic.js | 13 ++-- test/phantomjs/launch_app_and_login.js | 71 +++++++++++++++++++ 9 files changed, 127 insertions(+), 88 deletions(-) delete mode 100644 phantomjs/launch_app_and_login.js create mode 100644 test/gemini/tests/environment.js create mode 100644 test/phantomjs/launch_app_and_login.js diff --git a/phantomjs/launch_app_and_login.js b/phantomjs/launch_app_and_login.js deleted file mode 100644 index e5418519..00000000 --- a/phantomjs/launch_app_and_login.js +++ /dev/null @@ -1,62 +0,0 @@ -'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(); - } -}); diff --git a/test/gemini/README.md b/test/gemini/README.md index da1712d9..bc029041 100644 --- a/test/gemini/README.md +++ b/test/gemini/README.md @@ -122,9 +122,12 @@ See [the docs](https://github.com/gemini-testing/gemini/blob/master/doc/tests.md 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/`. +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 be nice if we kept the whitelabels up to date.** +**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 @@ -193,7 +196,7 @@ change the environment to run against. ```bash # In root /onion folder -phantomjs phantomjs/launch_app_and_login.js +phantomjs test/phantomjs/launch_app_and_login.js ``` diff --git a/test/gemini/tests/environment.js b/test/gemini/tests/environment.js new file mode 100644 index 00000000..4cb1dacf --- /dev/null +++ b/test/gemini/tests/environment.js @@ -0,0 +1,22 @@ +'use strict'; + +const mainUser = { + email: 'dimi@mailinator.com', + password: '0000000000' +}; +const mainPieceId = '12374'; +const mainEditionId = '14gw9x3VA9oJaxp4cHaAuK2bvJzvEj4Xvc'; + +console.log('================== Test environment ==================\n'); +console.log('Main user:'); +console.log(` Email: ${mainUser.email}`); +console.log(` Password: ${mainUser.password}\n`); +console.log(`Main piece: ${mainPieceId}`); +console.log(`Main edition: ${mainEditionId}\n`); +console.log('========================================================\n'); + +module.exports = { + mainUser, + mainPieceId, + mainEditionId +}; diff --git a/test/gemini/tests/main/authenticated.js b/test/gemini/tests/main/authenticated.js index bf579bff..52730d4c 100644 --- a/test/gemini/tests/main/authenticated.js +++ b/test/gemini/tests/main/authenticated.js @@ -1,6 +1,7 @@ 'use strict'; const gemini = require('gemini'); +const environment = require('../environment'); /** * Suite of tests against routes that require the user to be authenticated. @@ -25,8 +26,8 @@ gemini.suite('Authenticated', (suite) => { .capture('logged in', (actions, find) => { actions.waitForElementToShow('.ascribe-form', 5000); - actions.sendKeys(find('.ascribe-login-wrapper input[name=email]'), 'dimi@mailinator.com'); - actions.sendKeys(find('.ascribe-login-wrapper input[name=password]'), '0000000000'); + actions.sendKeys(find('.ascribe-login-wrapper input[name=email]'), environment.mainUser.email); + actions.sendKeys(find('.ascribe-login-wrapper input[name=password]'), environment.mainUser.password); actions.click(find('.ascribe-login-wrapper button[type=submit]')); actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', 5000); diff --git a/test/gemini/tests/main/basic.js b/test/gemini/tests/main/basic.js index 317c5d84..2e3dcb3a 100644 --- a/test/gemini/tests/main/basic.js +++ b/test/gemini/tests/main/basic.js @@ -1,6 +1,7 @@ 'use strict'; const gemini = require('gemini'); +const environment = require('../environment'); /** * Basic suite of tests against routes that do not require the user to be authenticated. @@ -84,8 +85,8 @@ gemini.suite('Basic', (suite) => { // Remove hover from sign up link actions.click(emailInput); - actions.sendKeys(emailInput, 'dimi@mailinator.com'); - actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); + actions.sendKeys(emailInput, environment.mainUser.email); + actions.sendKeys(find('.ascribe-form input[name=password]'), environment.mainUser.password); }) .capture('login form filled', (actions, find) => { actions.click(find('.ascribe-form-header')); @@ -99,9 +100,9 @@ gemini.suite('Basic', (suite) => { actions.waitForElementToShow('.ascribe-form', 5000); }) .capture('sign up form filled with focus', (actions, find) => { - actions.sendKeys(find('.ascribe-form input[name=email]'), 'dimi@mailinator.com'); - actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); - actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), '0000000000'); + actions.sendKeys(find('.ascribe-form input[name=email]'), environment.mainUser.email); + actions.sendKeys(find('.ascribe-form input[name=password]'), environment.mainUser.password); + actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), environment.mainUser.password); }) .capture('sign up form filled with check', (actions, find) => { actions.click(find('.ascribe-form input[type="checkbox"] ~ .checkbox')); @@ -115,7 +116,7 @@ gemini.suite('Basic', (suite) => { actions.waitForElementToShow('.ascribe-form', 5000); }) .capture('password reset form filled with focus', (actions, find) => { - actions.sendKeys(find('.ascribe-form input[name="email"]'), 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-form input[name="email"]'), environment.mainUser.email); }) .capture('password reset form filled', (actions, find) => { actions.click(find('.ascribe-form-header')); diff --git a/test/gemini/tests/main/detail.js b/test/gemini/tests/main/detail.js index 7adad5d4..fd07af6a 100644 --- a/test/gemini/tests/main/detail.js +++ b/test/gemini/tests/main/detail.js @@ -1,8 +1,9 @@ 'use strict'; const gemini = require('gemini'); -const pieceUrl = '/pieces/12374'; -const editionUrl = '/editions/14gw9x3VA9oJaxp4cHaAuK2bvJzvEj4Xvc'; +const environment = require('../environment'); +const pieceUrl = `/pieces/${environment.mainPieceId}`; +const editionUrl = `/editions/${environment.mainEditionId}`; /** * Suite of tests against the piece and edition routes. @@ -57,8 +58,8 @@ gemini.suite('Work detail', (suite) => { actions.waitForElementToShow('.ascribe-default-app', 5000); }) .capture('logged in', (actions, find) => { - actions.sendKeys(find('.ascribe-login-wrapper input[name=email]'), 'dimi@mailinator.com'); - actions.sendKeys(find('.ascribe-login-wrapper input[name=password]'), '0000000000'); + actions.sendKeys(find('.ascribe-login-wrapper input[name=email]'), environment.mainUser.email); + actions.sendKeys(find('.ascribe-login-wrapper input[name=password]'), environment.mainUser.password); actions.click(find('.ascribe-login-wrapper button[type=submit]')); actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', 5000); diff --git a/test/gemini/tests/whitelabel/ikonotv/ikonotv.js b/test/gemini/tests/whitelabel/ikonotv/ikonotv.js index 1741aaa0..05c36405 100644 --- a/test/gemini/tests/whitelabel/ikonotv/ikonotv.js +++ b/test/gemini/tests/whitelabel/ikonotv/ikonotv.js @@ -1,6 +1,7 @@ 'use strict'; const gemini = require('gemini'); +const environment = require('../environment'); /** * Suite of tests against Cyland specific routes @@ -68,8 +69,8 @@ gemini.suite('Ikonotv', (suite) => { // Remove hover from sign up link actions.click(emailInput); - actions.sendKeys(emailInput, 'dimi@mailinator.com'); - actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); + actions.sendKeys(emailInput, environment.mainUser.email); + actions.sendKeys(find('.ascribe-form input[name=password]'), environment.mainUser.password); }) .capture('login form filled', (actions, find) => { actions.click(find('.ascribe-form-header')); @@ -81,9 +82,9 @@ gemini.suite('Ikonotv', (suite) => { .setUrl('/signup') .capture('sign up') .capture('sign up form filled with focus', (actions, find) => { - actions.sendKeys(find('.ascribe-form input[name=email]'), 'dimi@mailinator.com'); - actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); - actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), '0000000000'); + actions.sendKeys(find('.ascribe-form input[name=email]'), environment.mainUser.email); + actions.sendKeys(find('.ascribe-form input[name=password]'), environment.mainUser.password); + actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), environment.mainUser.password); }) .capture('sign up form filled with check', (actions, find) => { actions.click(find('.ascribe-form input[type="checkbox"] ~ .checkbox')); diff --git a/test/gemini/tests/whitelabel/shared/whitelabel_basic.js b/test/gemini/tests/whitelabel/shared/whitelabel_basic.js index 7fe5c256..24aa432b 100644 --- a/test/gemini/tests/whitelabel/shared/whitelabel_basic.js +++ b/test/gemini/tests/whitelabel/shared/whitelabel_basic.js @@ -1,6 +1,7 @@ 'use strict'; const gemini = require('gemini'); +const environment = require('../environment'); /** * Basic suite of tests against whitelabel routes that do not require authentication. @@ -43,8 +44,8 @@ gemini.suite('Whitelabel basic', (suite) => { // Remove hover from sign up link actions.click(emailInput); - actions.sendKeys(emailInput, 'dimi@mailinator.com'); - actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); + actions.sendKeys(emailInput, environment.mainUser.email); + actions.sendKeys(find('.ascribe-form input[name=password]'), environment.mainUser.password); }) .capture('login form filled', (actions, find) => { actions.click(find('.ascribe-form-header')); @@ -62,9 +63,9 @@ gemini.suite('Whitelabel basic', (suite) => { actions.wait(500); }) .capture('sign up form filled with focus', (actions, find) => { - actions.sendKeys(find('.ascribe-form input[name=email]'), 'dimi@mailinator.com'); - actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); - actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), '0000000000'); + actions.sendKeys(find('.ascribe-form input[name=email]'), environment.mainUser.email); + actions.sendKeys(find('.ascribe-form input[name=password]'), environment.mainUser.password); + actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), environment.mainUser.password); }) .capture('sign up form filled with check', (actions, find) => { actions.click(find('.ascribe-form input[type="checkbox"] ~ .checkbox')); @@ -80,7 +81,7 @@ gemini.suite('Whitelabel basic', (suite) => { actions.wait(500); }) .capture('password reset form filled with focus', (actions, find) => { - actions.sendKeys(find('.ascribe-form input[name="email"]'), 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-form input[name="email"]'), environment.mainUser.email); }) .capture('password reset form filled', (actions, find) => { actions.click(find('.ascribe-form-header')); diff --git a/test/phantomjs/launch_app_and_login.js b/test/phantomjs/launch_app_and_login.js new file mode 100644 index 00000000..afc76f9e --- /dev/null +++ b/test/phantomjs/launch_app_and_login.js @@ -0,0 +1,71 @@ +'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'; + +function launchAppAndLogin(env) { + console.log('Running test to launch ' + env + ' and log into the app'); + + var page = require('webpage').create(); + page.open(localEnv, function(status) { + var attemptedToLogIn; + var loginCheckInterval; + + console.log('Load ' + env + ': ' + 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[name=email]'); + var password = inputForm.querySelector('input[name=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.'); + console.log('Edit the onion/test/phantomjs/launch_app_and_login.js file to do further actions after logging in.'); + console.log('Stopping phantomJS...'); + phantom.exit(); + } + }, 1000); + } else { + console.log('Something happened while trying to log in, aborting...'); + phantom.exit(); + } + + } else { + console.log('Failed to load page, exiing...'); + phantom.exit(); + } + }); +} + +launchAppAndLogin(localEnv); From 8ef19a1ed0f7a0a9248d35e339b2985effab73fe Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 1 Feb 2016 17:24:19 +0100 Subject: [PATCH 22/27] Fix prize instances of the verify route to be coa_verify --- js/components/whitelabel/prize/prize_routes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/components/whitelabel/prize/prize_routes.js b/js/components/whitelabel/prize/prize_routes.js index 5f80b30c..668bb1d5 100644 --- a/js/components/whitelabel/prize/prize_routes.js +++ b/js/components/whitelabel/prize/prize_routes.js @@ -57,7 +57,7 @@ const ROUTES = { headerTitle='COLLECTION'/> - + ), @@ -97,7 +97,7 @@ const ROUTES = { component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SPSettingsContainer)}/> - + ) From afc7bc7735093a6d3f7a59b09932e9801e0aa1d4 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 1 Feb 2016 17:48:49 +0100 Subject: [PATCH 23/27] Fix npm scripts for domain specific visual regression tests --- package.json | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 5e72b863..2048656c 100644 --- a/package.json +++ b/package.json @@ -18,15 +18,16 @@ "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": "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 -- --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" + "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": { "fineUploader": "./js/components/ascribe_uploader/vendor/s3.fine-uploader.js" From fd6371e2f1216746c099d660a4fafc6e09405eb1 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 1 Feb 2016 18:03:20 +0100 Subject: [PATCH 24/27] Small fixes for visual regression tests --- test/gemini/tests/main/authenticated.js | 3 ++- test/gemini/tests/whitelabel/ikonotv/ikonotv.js | 2 +- test/gemini/tests/whitelabel/shared/whitelabel_basic.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/gemini/tests/main/authenticated.js b/test/gemini/tests/main/authenticated.js index 52730d4c..5dc93562 100644 --- a/test/gemini/tests/main/authenticated.js +++ b/test/gemini/tests/main/authenticated.js @@ -101,7 +101,7 @@ gemini.suite('Authenticated', (suite) => { .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', 5000); + actions.waitForElementToShow('.ascribe-accordion-list-item-table', 10000); }) gemini.suite('Collection placeholder', (collectionPlaceholderSuite) => { @@ -133,6 +133,7 @@ gemini.suite('Authenticated', (suite) => { .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', 5000); }) gemini.suite('Order widget dropdown', (pieceListToolbarOrderWidgetSuite) => { diff --git a/test/gemini/tests/whitelabel/ikonotv/ikonotv.js b/test/gemini/tests/whitelabel/ikonotv/ikonotv.js index 05c36405..5bf019d8 100644 --- a/test/gemini/tests/whitelabel/ikonotv/ikonotv.js +++ b/test/gemini/tests/whitelabel/ikonotv/ikonotv.js @@ -1,7 +1,7 @@ 'use strict'; const gemini = require('gemini'); -const environment = require('../environment'); +const environment = require('../../environment'); /** * Suite of tests against Cyland specific routes diff --git a/test/gemini/tests/whitelabel/shared/whitelabel_basic.js b/test/gemini/tests/whitelabel/shared/whitelabel_basic.js index 24aa432b..25713994 100644 --- a/test/gemini/tests/whitelabel/shared/whitelabel_basic.js +++ b/test/gemini/tests/whitelabel/shared/whitelabel_basic.js @@ -1,7 +1,7 @@ 'use strict'; const gemini = require('gemini'); -const environment = require('../environment'); +const environment = require('../../environment'); /** * Basic suite of tests against whitelabel routes that do not require authentication. From f09201d8c35a2346effa938427a8b31cecf7e7f5 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 4 Feb 2016 11:59:25 +0100 Subject: [PATCH 25/27] Use environment to configure timeouts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apparently node doesn’t accept destructuring yet… --- test/gemini/tests/environment.js | 33 +++++++++++----- test/gemini/tests/main/authenticated.js | 38 ++++++++++--------- test/gemini/tests/main/basic.js | 30 ++++++++------- test/gemini/tests/main/detail.js | 29 +++++++------- test/gemini/tests/whitelabel/23vivi/23vivi.js | 6 ++- test/gemini/tests/whitelabel/cyland/cyland.js | 6 ++- .../tests/whitelabel/ikonotv/ikonotv.js | 22 ++++++----- .../tests/whitelabel/lumenus/lumenus.js | 6 ++- .../whitelabel/shared/whitelabel_basic.js | 26 +++++++------ 9 files changed, 113 insertions(+), 83 deletions(-) diff --git a/test/gemini/tests/environment.js b/test/gemini/tests/environment.js index 4cb1dacf..32f1c8ee 100644 --- a/test/gemini/tests/environment.js +++ b/test/gemini/tests/environment.js @@ -1,22 +1,35 @@ 'use strict'; -const mainUser = { +const MAIN_USER = { email: 'dimi@mailinator.com', password: '0000000000' }; -const mainPieceId = '12374'; -const mainEditionId = '14gw9x3VA9oJaxp4cHaAuK2bvJzvEj4Xvc'; +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: ${mainUser.email}`); -console.log(` Password: ${mainUser.password}\n`); -console.log(`Main piece: ${mainPieceId}`); -console.log(`Main edition: ${mainEditionId}\n`); +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 = { - mainUser, - mainPieceId, - mainEditionId + MAIN_USER, + MAIN_PIECE_ID, + MAIN_EDITION_ID, + TIMEOUTS }; diff --git a/test/gemini/tests/main/authenticated.js b/test/gemini/tests/main/authenticated.js index 5dc93562..7adfe16d 100644 --- a/test/gemini/tests/main/authenticated.js +++ b/test/gemini/tests/main/authenticated.js @@ -2,6 +2,8 @@ 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. @@ -15,7 +17,7 @@ gemini.suite('Authenticated', (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', 5000); + actions.waitForElementToShow('.ascribe-default-app', TIMEOUTS.NORMAL); }); // Suite just to log us in before any other suites run @@ -24,13 +26,13 @@ gemini.suite('Authenticated', (suite) => { .setUrl('/login') .ignoreElements('.ascribe-body') .capture('logged in', (actions, find) => { - actions.waitForElementToShow('.ascribe-form', 5000); + actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL); - actions.sendKeys(find('.ascribe-login-wrapper input[name=email]'), environment.mainUser.email); - actions.sendKeys(find('.ascribe-login-wrapper input[name=password]'), environment.mainUser.password); + 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)', 5000); + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL); }); }); @@ -41,7 +43,7 @@ gemini.suite('Authenticated', (suite) => { .ignoreElements('.client--cyland img.img-brand') .skip(/Mobile/) .before((actions, find) => { - actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', 5000); + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL); }) .capture('desktop header'); @@ -70,7 +72,7 @@ gemini.suite('Authenticated', (suite) => { .ignoreElements('.client--cyland img.img-brand') .skip(/Desktop/) .before((actions, find) => { - actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', 5000); + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL); }) .capture('mobile header') .capture('expanded mobile header', (actions, find) => { @@ -90,18 +92,18 @@ gemini.suite('Authenticated', (suite) => { collectionSuite .setCaptureElements('.ascribe-accordion-list') .before((actions, find) => { - actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', 5000); + 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(5000); + 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', 10000); + actions.waitForElementToShow('.ascribe-accordion-list-item-table', TIMEOUTS.LONG); }) gemini.suite('Collection placeholder', (collectionPlaceholderSuite) => { @@ -109,7 +111,7 @@ gemini.suite('Authenticated', (suite) => { .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', 5000); + actions.waitForElementToShow('.ascribe-accordion-list-placeholder', TIMEOUTS.NORMAL); }); }); @@ -119,7 +121,7 @@ gemini.suite('Authenticated', (suite) => { .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', 5000); + actions.waitForElementToShow('.ascribe-accordion-list-item-table', TIMEOUTS.NORMAL); actions.click('.ascribe-table thead tr input[type="checkbox"]'); actions.waitForElementToShow('.piece-list-bulk-modal'); @@ -133,7 +135,7 @@ gemini.suite('Authenticated', (suite) => { .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', 5000); + actions.waitForElementToShow('.ascribe-piece-list-toolbar .search-bar .icon-ascribe-search', TIMEOUTS.NORMAL); }) gemini.suite('Order widget dropdown', (pieceListToolbarOrderWidgetSuite) => { @@ -144,7 +146,7 @@ gemini.suite('Authenticated', (suite) => { 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)', 5000); + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL); }); }); @@ -156,7 +158,7 @@ gemini.suite('Authenticated', (suite) => { 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)', 5000); + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL); }); }); }); @@ -167,7 +169,7 @@ gemini.suite('Authenticated', (suite) => { .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', 5000); + 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'); @@ -199,7 +201,7 @@ gemini.suite('Authenticated', (suite) => { .before((actions, find) => { // This will be called before every nested suite begins unless that suite // also defines a `.before()` - actions.waitForElementToShow('.settings-container', 5000); + actions.waitForElementToShow('.settings-container', TIMEOUTS.NORMAL); }) .capture('user settings'); }); @@ -210,7 +212,7 @@ gemini.suite('Authenticated', (suite) => { .setUrl('/logout') .ignoreElements('.ascribe-body') .capture('logout', (actions, find) => { - actions.waitForElementToShow('.ascribe-login-wrapper', 10000); + actions.waitForElementToShow('.ascribe-login-wrapper', TIMEOUTS.LONG); }); }); }); diff --git a/test/gemini/tests/main/basic.js b/test/gemini/tests/main/basic.js index 2e3dcb3a..5657f77f 100644 --- a/test/gemini/tests/main/basic.js +++ b/test/gemini/tests/main/basic.js @@ -2,6 +2,8 @@ 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. @@ -14,7 +16,7 @@ gemini.suite('Basic', (suite) => { // 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', 5000); + actions.waitForElementToShow('.ascribe-default-app', TIMEOUTS.NORMAL); }); gemini.suite('Header-desktop', (headerSuite) => { @@ -22,7 +24,7 @@ gemini.suite('Basic', (suite) => { .setCaptureElements('nav.navbar .container') .skip(/Mobile/) .capture('desktop header', (actions, find) => { - actions.waitForElementToShow('nav.navbar .container', 5000); + actions.waitForElementToShow('nav.navbar .container', TIMEOUTS.NORMAL); }) .capture('hover on active item', (actions, find) => { const activeItem = find('nav.navbar li.active'); @@ -40,7 +42,7 @@ gemini.suite('Basic', (suite) => { .setCaptureElements('nav.navbar .container') .skip(/Desktop/) .capture('mobile header', (actions, find) => { - actions.waitForElementToShow('nav.navbar .container', 5000); + actions.waitForElementToShow('nav.navbar .container', TIMEOUTS.NORMAL); }) .capture('expanded mobile header', (actions, find) => { actions.click(find('nav.navbar .navbar-toggle')); @@ -56,7 +58,7 @@ gemini.suite('Basic', (suite) => { footerSuite .setCaptureElements('.ascribe-footer') .capture('footer', (actions, find) => { - actions.waitForElementToShow('.ascribe-footer', 5000); + actions.waitForElementToShow('.ascribe-footer', TIMEOUTS.NORMAL); }) .capture('hover on footer item', (actions, find) => { const footerItem = find('.ascribe-footer a:not(.social)'); @@ -71,7 +73,7 @@ gemini.suite('Basic', (suite) => { gemini.suite('Login', (loginSuite) => { loginSuite .capture('login', (actions, find) => { - actions.waitForElementToShow('.ascribe-form', 5000); + actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL); }) .capture('hover on login submit', (actions, find) => { actions.mouseMove(find('.ascribe-form button[type=submit]')); @@ -85,8 +87,8 @@ gemini.suite('Basic', (suite) => { // Remove hover from sign up link actions.click(emailInput); - actions.sendKeys(emailInput, environment.mainUser.email); - actions.sendKeys(find('.ascribe-form input[name=password]'), environment.mainUser.password); + 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')); @@ -97,12 +99,12 @@ gemini.suite('Basic', (suite) => { signUpSuite .setUrl('/signup') .capture('sign up', (actions, find) => { - actions.waitForElementToShow('.ascribe-form', 5000); + actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL); }) .capture('sign up form filled with focus', (actions, find) => { - actions.sendKeys(find('.ascribe-form input[name=email]'), environment.mainUser.email); - actions.sendKeys(find('.ascribe-form input[name=password]'), environment.mainUser.password); - actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), environment.mainUser.password); + 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')); @@ -113,10 +115,10 @@ gemini.suite('Basic', (suite) => { passwordResetSuite .setUrl('/password_reset') .capture('password reset', (actions, find) => { - actions.waitForElementToShow('.ascribe-form', 5000); + actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL); }) .capture('password reset form filled with focus', (actions, find) => { - actions.sendKeys(find('.ascribe-form input[name="email"]'), environment.mainUser.email); + actions.sendKeys(find('.ascribe-form input[name="email"]'), MAIN_USER.email); }) .capture('password reset form filled', (actions, find) => { actions.click(find('.ascribe-form-header')); @@ -127,7 +129,7 @@ gemini.suite('Basic', (suite) => { coaVerifySuite .setUrl('/coa_verify') .capture('coa verify', (actions, find) => { - actions.waitForElementToShow('.ascribe-form', 5000); + 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'); diff --git a/test/gemini/tests/main/detail.js b/test/gemini/tests/main/detail.js index fd07af6a..39e83d70 100644 --- a/test/gemini/tests/main/detail.js +++ b/test/gemini/tests/main/detail.js @@ -2,8 +2,11 @@ const gemini = require('gemini'); const environment = require('../environment'); -const pieceUrl = `/pieces/${environment.mainPieceId}`; -const editionUrl = `/editions/${environment.mainEditionId}`; +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. @@ -18,12 +21,12 @@ gemini.suite('Work detail', (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', 5000); + 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', 20000); - actions.waitForElementToShow('.ascribe-social-button-list .twitter-share-button', 20000); - actions.waitForElementToShow('.ascribe-media-player', 10000); + 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) => { @@ -36,7 +39,7 @@ gemini.suite('Work detail', (suite) => { setCaptureElements('.shmui-wrap') .capture('shmui', (actions, find) => { actions.click(find('.ascribe-media-player')); - actions.waitForElementToShow('.shmui-wrap:not(.loading)', 30000); + actions.waitForElementToShow('.shmui-wrap:not(.loading)', TIMEOUTS.SUPER_DUPER_EXTRA_LONG); // Wait for the transition to end actions.wait(1000); }); @@ -55,14 +58,14 @@ gemini.suite('Work detail', (suite) => { .setUrl('/login') .ignoreElements('.ascribe-body') .before((actions, find) => { - actions.waitForElementToShow('.ascribe-default-app', 5000); + actions.waitForElementToShow('.ascribe-default-app', TIMEOUTS.NORMAL); }) .capture('logged in', (actions, find) => { - actions.sendKeys(find('.ascribe-login-wrapper input[name=email]'), environment.mainUser.email); - actions.sendKeys(find('.ascribe-login-wrapper input[name=password]'), environment.mainUser.password); + 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)', 5000); + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL); }); }); @@ -122,10 +125,10 @@ gemini.suite('Work detail', (suite) => { .setUrl('/logout') .ignoreElements('.ascribe-body') .before((actions, find) => { - actions.waitForElementToShow('.ascribe-default-app', 5000); + actions.waitForElementToShow('.ascribe-default-app', TIMEOUTS.NORMAL); }) .capture('logout', (actions, find) => { - actions.waitForElementToShow('.ascribe-login-wrapper', 10000); + actions.waitForElementToShow('.ascribe-login-wrapper', TIMEOUTS.LONG); }); }); }); diff --git a/test/gemini/tests/whitelabel/23vivi/23vivi.js b/test/gemini/tests/whitelabel/23vivi/23vivi.js index cafdfc6d..99efb310 100644 --- a/test/gemini/tests/whitelabel/23vivi/23vivi.js +++ b/test/gemini/tests/whitelabel/23vivi/23vivi.js @@ -1,6 +1,8 @@ 'use strict'; const gemini = require('gemini'); +const environment = require('../../environment'); +const TIMEOUTS = environment.TIMEOUTS; /** * Suite of tests against 23vivi specific routes @@ -11,7 +13,7 @@ gemini.suite('23vivi', (suite) => { .setCaptureElements('.ascribe-wallet-app') .before((actions, find) => { // This will be called before every nested suite begins - actions.waitForElementToShow('.ascribe-wallet-app', 5000); + actions.waitForElementToShow('.ascribe-wallet-app', TIMEOUTS.NORMAL); }); gemini.suite('Landing', (landingSuite) => { @@ -19,7 +21,7 @@ gemini.suite('23vivi', (suite) => { .setUrl('/') .capture('landing', (actions, find) => { // Wait for the logo to appear - actions.waitForElementToShow('.vivi23-landing--header-logo', 10000); + actions.waitForElementToShow('.vivi23-landing--header-logo', TIMEOUTS.LONG); }); }); diff --git a/test/gemini/tests/whitelabel/cyland/cyland.js b/test/gemini/tests/whitelabel/cyland/cyland.js index 06709f39..8159f53e 100644 --- a/test/gemini/tests/whitelabel/cyland/cyland.js +++ b/test/gemini/tests/whitelabel/cyland/cyland.js @@ -1,6 +1,8 @@ 'use strict'; const gemini = require('gemini'); +const environment = require('../../environment'); +const TIMEOUTS = environment.TIMEOUTS; /** * Suite of tests against Cyland specific routes @@ -11,7 +13,7 @@ gemini.suite('Cyland', (suite) => { .setCaptureElements('.ascribe-wallet-app') .before((actions, find) => { // This will be called before every nested suite begins - actions.waitForElementToShow('.ascribe-wallet-app', 5000); + actions.waitForElementToShow('.ascribe-wallet-app', TIMEOUTS.NORMAL); }); gemini.suite('Landing', (landingSuite) => { @@ -20,7 +22,7 @@ gemini.suite('Cyland', (suite) => { // Ignore Cyland's logo as it's a gif .ignoreElements('.cyland-landing img') .capture('landing', (actions, find) => { - actions.waitForElementToShow('.cyland-landing img', 10000); + actions.waitForElementToShow('.cyland-landing img', TIMEOUTS.LONG); }); }); diff --git a/test/gemini/tests/whitelabel/ikonotv/ikonotv.js b/test/gemini/tests/whitelabel/ikonotv/ikonotv.js index 5bf019d8..84d743c0 100644 --- a/test/gemini/tests/whitelabel/ikonotv/ikonotv.js +++ b/test/gemini/tests/whitelabel/ikonotv/ikonotv.js @@ -2,6 +2,8 @@ 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 @@ -12,7 +14,7 @@ gemini.suite('Ikonotv', (suite) => { .setCaptureElements('.ascribe-wallet-app') .before((actions, find) => { // This will be called before every nested suite begins - actions.waitForElementToShow('.ascribe-wallet-app', 5000); + actions.waitForElementToShow('.ascribe-wallet-app', TIMEOUTS.NORMAL); }); gemini.suite('Landing', (landingSuite) => { @@ -30,7 +32,7 @@ gemini.suite('Ikonotv', (suite) => { }); // Wait for logo to appear - actions.waitForElementToShow('.ikonotv-landing header img', 10000); + actions.waitForElementToShow('.ikonotv-landing header img', TIMEOUTS.LONG); }); }); @@ -44,13 +46,13 @@ gemini.suite('Ikonotv', (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', 5000); + actions.waitForElementToShow('.ascribe-wallet-app', TIMEOUTS.NORMAL); // Wait for the forms to appear - actions.waitForElementToShow('.ascribe-form', 5000); + actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL); // Just use a dumb wait because the logo is set as a background image - actions.wait(3000); + actions.wait(TIMEOUTS.SHORT); }); gemini.suite('Login', (loginSuite) => { @@ -69,8 +71,8 @@ gemini.suite('Ikonotv', (suite) => { // Remove hover from sign up link actions.click(emailInput); - actions.sendKeys(emailInput, environment.mainUser.email); - actions.sendKeys(find('.ascribe-form input[name=password]'), environment.mainUser.password); + 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')); @@ -82,9 +84,9 @@ gemini.suite('Ikonotv', (suite) => { .setUrl('/signup') .capture('sign up') .capture('sign up form filled with focus', (actions, find) => { - actions.sendKeys(find('.ascribe-form input[name=email]'), environment.mainUser.email); - actions.sendKeys(find('.ascribe-form input[name=password]'), environment.mainUser.password); - actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), environment.mainUser.password); + 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')); diff --git a/test/gemini/tests/whitelabel/lumenus/lumenus.js b/test/gemini/tests/whitelabel/lumenus/lumenus.js index a9ff53cd..8e8f568d 100644 --- a/test/gemini/tests/whitelabel/lumenus/lumenus.js +++ b/test/gemini/tests/whitelabel/lumenus/lumenus.js @@ -1,6 +1,8 @@ 'use strict'; const gemini = require('gemini'); +const environment = require('../../environment'); +const TIMEOUTS = environment.TIMEOUTS; /** * Suite of tests against lumenus specific routes @@ -11,7 +13,7 @@ gemini.suite('Lumenus', (suite) => { .setCaptureElements('.ascribe-wallet-app') .before((actions, find) => { // This will be called before every nested suite begins - actions.waitForElementToShow('.ascribe-wallet-app', 5000); + actions.waitForElementToShow('.ascribe-wallet-app', TIMEOUTS.NORMAL); }); gemini.suite('Landing', (landingSuite) => { @@ -19,7 +21,7 @@ gemini.suite('Lumenus', (suite) => { .setUrl('/') .capture('landing', (actions, find) => { // Wait for the logo to appear - actions.waitForElementToShow('.wp-landing-wrapper img', 10000); + actions.waitForElementToShow('.wp-landing-wrapper img', TIMEOUTS.LONG); }); }); diff --git a/test/gemini/tests/whitelabel/shared/whitelabel_basic.js b/test/gemini/tests/whitelabel/shared/whitelabel_basic.js index 25713994..0d5ac26b 100644 --- a/test/gemini/tests/whitelabel/shared/whitelabel_basic.js +++ b/test/gemini/tests/whitelabel/shared/whitelabel_basic.js @@ -2,6 +2,8 @@ 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. @@ -13,7 +15,7 @@ gemini.suite('Whitelabel basic', (suite) => { // 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', 5000); + 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); @@ -25,12 +27,12 @@ gemini.suite('Whitelabel basic', (suite) => { // See Ikono .skip(/Ikono/) .capture('login', (actions, find) => { - actions.waitForElementToShow('.ascribe-form', 5000); + 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(3000); + actions.wait(TIMEOUTS.SHORT); }) .capture('hover on login submit', (actions, find) => { actions.mouseMove(find('.ascribe-form button[type=submit]')); @@ -44,8 +46,8 @@ gemini.suite('Whitelabel basic', (suite) => { // Remove hover from sign up link actions.click(emailInput); - actions.sendKeys(emailInput, environment.mainUser.email); - actions.sendKeys(find('.ascribe-form input[name=password]'), environment.mainUser.password); + 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')); @@ -58,14 +60,14 @@ gemini.suite('Whitelabel basic', (suite) => { // See Ikono .skip(/Ikono/) .capture('sign up', (actions, find) => { - actions.waitForElementToShow('.ascribe-form', 5000); + 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]'), environment.mainUser.email); - actions.sendKeys(find('.ascribe-form input[name=password]'), environment.mainUser.password); - actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), environment.mainUser.password); + 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')); @@ -76,12 +78,12 @@ gemini.suite('Whitelabel basic', (suite) => { passwordResetSuite .setUrl('/password_reset') .capture('password reset', (actions, find) => { - actions.waitForElementToShow('.ascribe-form', 5000); + 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"]'), environment.mainUser.email); + actions.sendKeys(find('.ascribe-form input[name="email"]'), MAIN_USER.email); }) .capture('password reset form filled', (actions, find) => { actions.click(find('.ascribe-form-header')); @@ -92,7 +94,7 @@ gemini.suite('Whitelabel basic', (suite) => { coaVerifySuite .setUrl('/coa_verify') .capture('coa verify', (actions, find) => { - actions.waitForElementToShow('.ascribe-form', 5000); + actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL); // Wait in case the form reloads due to other assets loading actions.wait(500); }) From 02c25c323d74a0711fbe73a26cc929ed0ae0574d Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 4 Feb 2016 12:00:05 +0100 Subject: [PATCH 26/27] Ignore locally generated screenshots Too many minor differences with PhantomJS between OSX/Linux --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0b4d85c7..e497465f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ build/* gemini-coverage/* gemini-report/* +test/gemini/screenshots/* node_modules/* From 6362a3651f0288f912e0a22d6ea5859626357d62 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 4 Feb 2016 12:02:12 +0100 Subject: [PATCH 27/27] Remove phantomJS test file --- test/gemini/README.md | 6 +-- test/phantomjs/launch_app_and_login.js | 71 -------------------------- 2 files changed, 3 insertions(+), 74 deletions(-) delete mode 100644 test/phantomjs/launch_app_and_login.js diff --git a/test/gemini/README.md b/test/gemini/README.md index bc029041..3521056e 100644 --- a/test/gemini/README.md +++ b/test/gemini/README.md @@ -190,9 +190,9 @@ 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. +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 diff --git a/test/phantomjs/launch_app_and_login.js b/test/phantomjs/launch_app_and_login.js deleted file mode 100644 index afc76f9e..00000000 --- a/test/phantomjs/launch_app_and_login.js +++ /dev/null @@ -1,71 +0,0 @@ -'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'; - -function launchAppAndLogin(env) { - console.log('Running test to launch ' + env + ' and log into the app'); - - var page = require('webpage').create(); - page.open(localEnv, function(status) { - var attemptedToLogIn; - var loginCheckInterval; - - console.log('Load ' + env + ': ' + 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[name=email]'); - var password = inputForm.querySelector('input[name=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.'); - console.log('Edit the onion/test/phantomjs/launch_app_and_login.js file to do further actions after logging in.'); - console.log('Stopping phantomJS...'); - phantom.exit(); - } - }, 1000); - } else { - console.log('Something happened while trying to log in, aborting...'); - phantom.exit(); - } - - } else { - console.log('Failed to load page, exiing...'); - phantom.exit(); - } - }); -} - -launchAppAndLogin(localEnv);