From 1d09ef1120bf9f05e00081ab6f8b09fff569b0bb Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 11:21:57 +0100 Subject: [PATCH] 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); + }); + }); +});