Merge branch 'next' into user-status-refactor

This commit is contained in:
Wyatt Johnson
2017-11-13 16:06:16 -07:00
37 changed files with 1025 additions and 493 deletions
@@ -32,8 +32,9 @@ class ActionsMenu extends React.Component {
};
render() {
const {className = ''} = this.props;
return (
<div className={styles.root} onBlur={this.syncOpenState} >
<div className={cn(styles.root, className)} onBlur={this.syncOpenState} >
<Button
cStyle='actions'
className={cn(styles.button, {[styles.buttonOpen]: this.state.open})}
@@ -59,6 +60,7 @@ class ActionsMenu extends React.Component {
ActionsMenu.propTypes = {
icon: PropTypes.string,
children: PropTypes.node,
className: PropTypes.string,
};
export default ActionsMenu;
@@ -3,12 +3,14 @@ import cn from 'classnames';
import {MenuItem} from 'react-mdl';
import PropTypes from 'prop-types';
import styles from './ActionsMenu.css';
import camelCase from 'lodash/camelCase';
const ActionsMenuItem = (props) =>
<MenuItem className={cn(styles.menuItem, props.className)} {...props} />;
<MenuItem className={cn(styles.menuItem, props.className, 'action-menu-item')} {...props} id={camelCase(props.children)}/>;
ActionsMenuItem.propTypes = {
className: PropTypes.string,
children: PropTypes.string,
};
export default ActionsMenuItem;
@@ -1,4 +1,5 @@
import React from 'react';
import cn from 'classnames';
import PropTypes from 'prop-types';
import {Dialog} from 'coral-ui';
import styles from './BanUserDialog.css';
@@ -8,28 +9,34 @@ import t from 'coral-framework/services/i18n';
const BanUserDialog = ({open, onCancel, onPerform, username, info}) => (
<Dialog
className={styles.dialog}
className={cn(styles.dialog, 'talk-ban-user-dialog')}
id="banUserDialog"
open={open}
onCancel={onCancel}
title={t('bandialog.ban_user')}>
<span className={styles.close} onClick={onCancel}>×</span>
<div>
<div className={styles.header}>
<h2>{t('bandialog.ban_user')}</h2>
</div>
<div className={styles.separator}>
<h3>{t('bandialog.are_you_sure', username)}</h3>
<i>{info}</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={onCancel} raised>
{t('bandialog.cancel')}
</Button>
<Button cStyle="black" className={styles.ban} onClick={onPerform} raised>
{t('bandialog.yes_ban_user')}
</Button>
</div>
<div className={styles.header}>
<h2>{t('bandialog.ban_user')}</h2>
</div>
<div className={styles.separator}>
<h3>{t('bandialog.are_you_sure', username)}</h3>
<i>{info}</i>
</div>
<div className={styles.buttons}>
<Button
className={cn(styles.cancel, 'talk-ban-user-dialog-button-cancel')}
cStyle="cancel"
onClick={onCancel}
raised >
{t('bandialog.cancel')}
</Button>
<Button
className={cn(styles.ban, 'talk-ban-user-dialog-button-confirm')}
cStyle="black"
onClick={onPerform}
raised >
{t('bandialog.yes_ban_user')}
</Button>
</div>
</Dialog>
);
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import {Dialog} from 'coral-ui';
import {RadioGroup, Radio} from 'react-mdl';
import styles from './SuspendUserDialog.css';
import cn from 'classnames';
import Button from 'coral-ui/components/Button';
@@ -87,7 +88,7 @@ class SuspendUserDialog extends React.Component {
<Button cStyle="white" className={styles.cancel} onClick={onCancel} raised>
{t('suspenduser.cancel')}
</Button>
<Button cStyle="black" className={styles.perform} onClick={this.goToStep1} raised>
<Button cStyle="black" className={cn(styles.perform, 'talk-admin-suspend-user-dialog-confirm')} onClick={this.goToStep1} raised>
{t('suspenduser.suspend_user')}
</Button>
</div>
@@ -120,7 +121,7 @@ class SuspendUserDialog extends React.Component {
</Button>
<Button
cStyle="black"
className={styles.perform}
className={cn(styles.perform, 'talk-admin-suspend-user-dialog-send')}
onClick={this.handlePerform}
disabled={this.state.message.length === 0}
raised
@@ -137,7 +138,7 @@ class SuspendUserDialog extends React.Component {
const {step} = this.state;
return (
<Dialog
className={styles.dialog}
className={cn(styles.dialog, 'talk-admin-suspend-user-dialog')}
onCancel={onCancel}
open={open}
>
@@ -1,4 +1,5 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import SuspendUserDialog from '../components/SuspendUserDialog';
@@ -44,6 +45,12 @@ class SuspendUserDialogContainer extends Component {
}
}
SuspendUserDialogContainer.propTypes = {
open: PropTypes.bool,
hideSuspendUserDialog: PropTypes.func,
username: PropTypes.object,
};
const withOrganizationName = withQuery(gql`
query CoralAdmin_SuspendUserDialog {
__typename
@@ -13,11 +13,11 @@ const CommunityMenu = ({flaggedUsernamesCount = 0}) => {
<div className='mdl-tabs'>
<div className={`mdl-tabs__tab-bar ${styles.tabBar}`}>
<div>
<Link to={flaggedPath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
<Link to={flaggedPath} className={`mdl-tabs__tab ${styles.tab} talk-admin-nav-flagged-accounts`} activeClassName={styles.active}>
{t('community.flaggedaccounts')}
<CountBadge count={flaggedUsernamesCount} />
</Link>
<Link to={peoplePath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
<Link to={peoplePath} className={`mdl-tabs__tab ${styles.tab} talk-admin-nav-people`} activeClassName={styles.active}>
{t('community.people')}
</Link>
</div>
@@ -1,4 +1,5 @@
import React from 'react';
import cn from 'classnames';
import styles from './styles.css';
import Table from './Table';
import {Icon} from 'coral-ui';
@@ -24,7 +25,7 @@ const People = (props) => {
const hasResults = !!users.length;
return (
<div className={styles.container}>
<div className={cn(styles.container, 'talk-admin-community-people-container')}>
<div className={styles.leftColumn}>
<div className={styles.searchBox}>
<Icon name='search' className={styles.searchIcon}/>
@@ -42,7 +42,7 @@ const Table = ({users, setRole, onHeaderClickHandler, setCommenterStatus, viewUs
</thead>
<tbody>
{users.map((row, i)=> (
<tr key={i}>
<tr key={i} className="talk-admin-community-people-row">
<td className="mdl-data-table__cell--non-numeric">
<button onClick={() => {viewUserDetail(row.id);}} className={cn(styles.username, styles.button)}>{row.username}</button>
<span className={styles.email}>{row.profiles.map(({id}) => id)}</span>
@@ -52,6 +52,7 @@ const Table = ({users, setRole, onHeaderClickHandler, setCommenterStatus, viewUs
</td>
<td className="mdl-data-table__cell--non-numeric">
<Dropdown
containerClassName="talk-admin-community-people-dd-status"
value={row.status}
placeholder={t('community.status')}
onChange={(status) => setCommenterStatus(row.id, status)}>
@@ -61,6 +62,7 @@ const Table = ({users, setRole, onHeaderClickHandler, setCommenterStatus, viewUs
</td>
<td className="mdl-data-table__cell--non-numeric">
<Dropdown
containerClassName="talk-admin-community-people-dd-role"
value={row.roles[0] || ''}
placeholder={t('community.role')}
onChange={(role) => setRole(row.id, role)}>
@@ -73,7 +73,7 @@ class Comment extends React.Component {
return (
<li
tabIndex={0}
className={cn(className, 'mdl-card', selectionStateCSS, styles.root, {[styles.selected]: selected})}
className={cn(className, 'mdl-card', selectionStateCSS, styles.root, {[styles.selected]: selected}, 'talk-admin-moderate-comment')}
id={`comment_${comment.id}`}
>
<div className={styles.container}>
@@ -95,7 +95,7 @@ class Comment extends React.Component {
: null
}
{currentUserId !== comment.user.id &&
<ActionsMenu icon="not_interested">
<ActionsMenu icon="not_interested" className="talk-admin-moderate-comment-actions-menu">
<ActionsMenuItem
disabled={comment.user.status === 'BANNED'}
onClick={this.showSuspendUserDialog}>
+2 -2
View File
@@ -10,12 +10,12 @@ export default class Dialog extends Component {
onCancel: PropTypes.func,
onClose: PropTypes.func,
open: PropTypes.bool,
style: PropTypes.object
style: PropTypes.object,
};
static defaultProps = {
onCancel: (e) => e.preventDefault(),
onClose: (e) => e.preventDefault()
onClose: (e) => e.preventDefault(),
};
componentDidMount(){
+2 -2
View File
@@ -131,7 +131,7 @@ class Dropdown extends React.Component {
const {containerClassName, toggleClassName, toggleOpenClassName} = this.props;
return (
<ClickOutside onClickOutside={this.hideMenu}>
<div className={cn(styles.dropdown, containerClassName)}>
<div className={cn(styles.dropdown, containerClassName, 'dd dd-container')}>
<div
className={cn(styles.toggle, toggleClassName, {[cn(this.state.isOpen, toggleOpenClassName)]: this.state.isOpen})}
onClick={this.handleClick}
@@ -149,7 +149,7 @@ class Dropdown extends React.Component {
{this.state.isOpen &&
<div>
<div tabIndex="0" onFocus={this.trapFocusBegin} />
<ul className={cn(styles.list, {[styles.listActive] : this.state.isOpen})}>
<ul className={cn(styles.list, {[styles.listActive] : this.state.isOpen}, 'dd-list-active')}>
{React.Children.toArray(this.props.children)
.map((child, i) =>
React.cloneElement(child, {
+3 -1
View File
@@ -16,8 +16,9 @@ class Option extends React.Component {
render() {
const {className, label = '', onClick, onKeyDown} = this.props;
const id = this.props.id ? this.props.id : this.props.value;
return (
<li className={cn(styles.option, className)} onClick={onClick} onKeyDown={onKeyDown} role="option" tabIndex="0" ref={this.handleRef}>
<li className={cn(styles.option, className, 'dd-option')} onClick={onClick} onKeyDown={onKeyDown} role="option" tabIndex="0" ref={this.handleRef} id={id}>
{label}
</li>
);
@@ -26,6 +27,7 @@ class Option extends React.Component {
Option.propTypes = {
className: PropTypes.string,
id: PropTypes.string,
label: PropTypes.string,
onClick: PropTypes.func,
onKeyDown: PropTypes.func,
+2 -2
View File
@@ -369,12 +369,12 @@ Then all the routes for the API will be expecting to be hit on `/talk/`, such as
can perform the path stripping when serving an upstream proxy, but some CDN's
cannot. You would use this option in the latter situation.
## TALK_SMTP_EMAIL
## TALK_SMTP_FROM_ADDRESS
The email address to send emails from using the SMTP provider in the format:
```plain
TALK_SMTP_EMAIL="The Coral Project" <support@coralproject.net>
TALK_SMTP_FROM_ADDRESS="The Coral Project" <support@coralproject.net>
```
Including the name and email address.
+2 -1
View File
@@ -22,11 +22,12 @@ const nightwatch_config = {
'browserstack.user': process.env.BROWSERSTACK_USER || 'coralproject2',
'browserstack.key': process.env.BROWSERSTACK_KEY,
'browserstack.local': true,
'browserstack.localIdentifier': process.env.BROWSERSTACK_LOCAL_IDENTIFIER ? process.env.BROWSERSTACK_LOCAL_IDENTIFIER : undefined,
'browserstack.debug': true,
// Disable this, as it makes bs slow and brittle.
'browserstack.networkLogs': false,
'browserstack.resolution': '1600x1200',
'resolution': '1600x1200',
},
screenshots : {
enabled: true,
+2
View File
@@ -40,6 +40,8 @@ module.exports = {
path: process.env.REPORTS_FOLDER || './test/e2e/reports',
},
},
'chrome': {
},
'chrome-headless': {
desiredCapabilities: {
chromeOptions : {
+2 -6
View File
@@ -12,7 +12,7 @@
"watch:client": "NODE_ENV=development webpack --progress --watch",
"watch:server": "nodemon --config .nodemon.json",
"start:development": "NODE_ENV=development ./bin/cli -c .env serve -j -w",
"start:production": "NODE_ENV=production ./bin/cli serve -j -w",
"start": "NODE_ENV=production ./bin/cli serve -j -w",
"prebuild": "npm-run-all clean generate-introspection",
"build": "NODE_ENV=production webpack -p --bail",
"lint:yaml": "yamllint locales/*.yml",
@@ -23,12 +23,8 @@
"test:server": "TEST_MODE=unit NODE_ENV=test mocha -R ${MOCHA_REPORTER:-spec}",
"test:client": "TEST_MODE=unit NODE_ENV=test jest",
"test:client:watch": "TEST_MODE=unit NODE_ENV=test jest --watch",
"pree2e": "selenium-standalone install",
"pree2e:ci": "selenium-standalone install",
"pree2e:browserstack": "selenium-standalone install",
"e2e": "NODE_ENV=test nightwatch",
"e2e": "./scripts/e2e.js",
"e2e:ci": "./scripts/e2e-ci.sh",
"e2e:browserstack": "NODE_ENV=test ./scripts/e2e-browserstack.js --config nightwatch-browserstack.conf.js",
"heroku-postbuild": "npm-run-all plugins:reconcile build"
},
"talk": {
@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './styles.css';
import {t} from 'plugin-api/beta/client/services';
import {Icon} from 'plugin-api/beta/client/components/ui';
@@ -6,16 +7,23 @@ import cn from 'classnames';
const isApproved = (status) => (status === 'ACCEPTED');
export default ({approveComment, comment: {status}}) => (
const ApproveCommentAction = ({approveComment, comment: {status}}) => (
isApproved(status) ? (
<span className={styles.approved}>
<Icon name="check_circle" className={styles.icon} />
{t('talk-plugin-moderation-actions.approved_comment')}
</span>
) : (
<button className={cn(styles.button, 'talk-plugin-moderation-actions-reject')} onClick={approveComment}>
<button className={cn(styles.button, 'talk-plugin-moderation-actions-approve')} onClick={approveComment}>
<Icon name="done" className={styles.icon} />
{t('talk-plugin-moderation-actions.approve_comment')}
</button>
)
);
ApproveCommentAction.propTypes = {
approveComment: PropTypes.func,
comment: PropTypes.object,
};
export default ApproveCommentAction;
@@ -1,14 +1,21 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './styles.css';
import {t} from 'plugin-api/beta/client/services';
import {Icon} from 'plugin-api/beta/client/components/ui';
import cn from 'classnames';
export default ({onBanUser}) => (
const BanUserAction = ({onBanUser}) => (
<button
className={cn(styles.button, 'talk-plugin-moderation-actions-reject')}
className={cn(styles.button, 'talk-plugin-moderation-actions-ban')}
onClick={onBanUser} >
<Icon name="block" className={styles.icon} />
{t('talk-plugin-moderation-actions.ban_user')}
</button>
);
BanUserAction.propTypes = {
onBanUser: PropTypes.func,
};
export default BanUserAction;
@@ -1,10 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import styles from './BanUserDialog.css';
import {t} from 'plugin-api/beta/client/services';
import {Dialog, Button} from 'plugin-api/beta/client/components/ui';
export default ({showBanDialog, closeBanDialog, banUser}) => (
<Dialog open={showBanDialog} className={styles.dialog}>
const BanUserDialog = ({showBanDialog, closeBanDialog, banUser}) => (
<Dialog open={showBanDialog} className={cn(styles.dialog, 'talk-ban-user-dialog')}>
<span className={styles.close} onClick={closeBanDialog}>×</span>
<h2>{t('talk-plugin-moderation-actions.ban_user_dialog_headline')}</h2>
<h3>{t('talk-plugin-moderation-actions.ban_user_dialog_sub')}</h3>
@@ -12,12 +14,28 @@ export default ({showBanDialog, closeBanDialog, banUser}) => (
{t('talk-plugin-moderation-actions.ban_user_dialog_copy')}
</p>
<div className={styles.buttons}>
<Button cStyle="cancel" onClick={closeBanDialog} className={styles.cancel} raised>
<Button
cStyle="cancel"
onClick={closeBanDialog}
className={cn(styles.cancel, 'talk-ban-user-dialog-button-cancel')}
raised >
{t('talk-plugin-moderation-actions.ban_user_dialog_cancel')}
</Button>
<Button cStyle="black" onClick={banUser} className={styles.confirm} raised>
<Button
cStyle="black"
onClick={banUser}
className={cn(styles.confirm, 'talk-ban-user-dialog-button-confirm')}
raised >
{t('talk-plugin-moderation-actions.ban_user_dialog_yes')}
</Button>
</div>
</Dialog>
);
BanUserDialog.propTypes = {
showBanDialog: PropTypes.func.isRequired,
closeBanDialog: PropTypes.func.isRequired,
banUser: PropTypes.func.isRequired,
};
export default BanUserDialog;
@@ -1,9 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import styles from './Menu.css';
import {t} from 'plugin-api/beta/client/services';
export default ({className = '', children}) => (
const Menu = ({className = '', children}) => (
<div className={cn(styles.menu, className)}>
<h3 className={styles.headline}>
{t('talk-plugin-moderation-actions.moderation_actions')}
@@ -11,3 +12,10 @@ export default ({className = '', children}) => (
{children}
</div>
);
Menu.propTypes = {
className: PropTypes.string,
children: PropTypes.node,
};
export default Menu;
@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import Menu from './Menu';
import styles from './ModerationActions.css';
@@ -21,7 +22,7 @@ export default class ModerationActions extends React.Component {
<Icon name="keyboard_arrow_down" className={styles.icon} />}
</span>
{menuVisible && (
<Menu>
<Menu className="talk-plugin-modetarion-actions-menu">
<Slot
className="talk-plugin-modetarion-actions-slot"
fill="moderationActions"
@@ -38,3 +39,13 @@ export default class ModerationActions extends React.Component {
);
}
}
ModerationActions.propTypes = {
comment: PropTypes.object,
root: PropTypes.object,
asset: PropTypes.object,
data: PropTypes.object,
menuVisible: PropTypes.bool,
toogleMenu: PropTypes.func,
hideMenu: PropTypes.func,
};
@@ -1,12 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import styles from './styles.css';
import {t} from 'plugin-api/beta/client/services';
import {Icon} from 'plugin-api/beta/client/components/ui';
export default ({rejectComment}) => (
const RejectCommentAction = ({rejectComment}) => (
<button className={cn(styles.button, 'talk-plugin-moderation-actions-reject')} onClick={rejectComment}>
<Icon name="clear" className={styles.icon} />
{t('talk-plugin-moderation-actions.reject_comment')}
</button>
);
RejectCommentAction.propTypes = {
rejectComment: PropTypes.func,
};
export default RejectCommentAction;
-51
View File
@@ -1,51 +0,0 @@
#!/usr/bin/env node
const Nightwatch = require('nightwatch');
const browserstack = require('browserstack-local');
const {onshutdown} = require('../bin/util');
async function start() {
try {
const bs_local = new browserstack.Local();
process.mainModule.filename = './node_modules/.bin/nightwatch';
// Code to start browserstack local before start of test
console.log('Connecting local');
Nightwatch.bs_local = bs_local;
bs_local.start({
'key': process.env.BROWSERSTACK_KEY,
'logFile': './test/e2e/bslocal.log'
}, function(error) {
if (error) {
console.error(error);
throw error;
}
console.log('Connected. Now testing...');
Nightwatch.cli(function(argv) {
Nightwatch.CliRunner(argv)
.setup(null, function(){
// Code to stop browserstack local after end of parallel test
bs_local.stop(function(){});
})
.runTests(function(){
// Code to stop browserstack local after end of single test
bs_local.stop(function(){});
});
});
});
onshutdown([
() => bs_local.stop(function(){}),
]);
} catch (ex) {
console.log('There was an error while starting the test runner:\n\n');
process.stderr.write(`${ex.stack}\n`);
process.exit(2);
}
}
start();
+8 -80
View File
@@ -1,92 +1,20 @@
#!/bin/bash
CIRCLE_TEST_REPORTS=${CIRCLE_TEST_REPORTS:-./test/e2e/reports}
REPORTS_FOLDER=${CIRCLE_TEST_REPORTS:-./test/e2e/reports}
CIRCLE_BRANCH=${CIRCLE_BRANCH:-master}
# Amount of retries before failure.
E2E_MAX_RETRIES=${E2E_MAX_RETRIES:-1}
# Amount of seconds between tests.
E2E_SLEEP_BETWEEN_TESTS=${E2E_SLEEP_BETWEEN_TESTS:-1}
# Safari >= 8 has issues connecting to browserstack-local. Safari < 8 is too old.
BROWSERS="chrome firefox ie edge" #safari
# IE 64bit has issues with receiving keyboard input. Let's wait for them to fix it.
BROWSERS="chrome,firefox,edge" #ie safari
if [[ "${CIRCLE_BRANCH}" == "master" ]]; then
# List of failed browsers.
failedBrowsers=
# List of succeeded browsers.
succeededBrowsers=
exitCode=0
browserstack() {
# Current number of tries.
try=${2:-0}
echo "-- Start e2e for $1 #$try --"
REPORTS_FOLDER="$CIRCLE_TEST_REPORTS/$1" yarn e2e:browserstack --env "$1"
# Determine exit code.
result=$?
if [ "$result" -ne "0" ]; then
echo "-- Failed e2e for $1 #$try --"
# Try again until E2E_MAX_RETRIES is reached.
if [ "$try" -lt "$E2E_MAX_RETRIES" ]; then
let try=try+1
# Sleep a bit to let browserstack-local close properly.
sleep "$E2E_SLEEP_BETWEEN_TESTS"
browserstack "$1" "$try"
return
fi
# Failed, add to list of failed browsers.
failedBrowsers="$failedBrowsers $1"
# Remember exit code.
exitCode=$result
else
echo "-- Success e2e for $1 #$try --"
# Succeeded, add to list of succeeded browsers.
succeededBrowsers="$succeededBrowsers $1"
eval "browser_${1}_succeeded_at=$try"
fi
# Sleep a bit to let browserstack-local close properly.
sleep "$E2E_SLEEP_BETWEEN_TESTS"
}
# Test using browserstack.
for browser in $BROWSERS
do
browserstack "$browser"
done
# Print information about succeeded browsers.
for x in $succeededBrowsers
do
echo "Succeeded $x at try #$(eval "echo \$browser_${x}_succeeded_at")"
done
# Print information about failed browsers.
for x in $failedBrowsers
do
echo "Failed $x"
done
exit $exitCode
if [[ "${CIRCLE_BRANCH}" == "master" && -n "$BROWSERSTACK_KEY" ]]; then
echo Testing on browserstack
yarn e2e --reports-folder "$REPORTS_FOLDER" --bs-key "$BROWSERSTACK_KEY" --retries "$E2E_MAX_RETRIES" --browsers $BROWSERS
else
# When browserstack is not available test locally using chrome headless.
REPORTS_FOLDER="$CIRCLE_TEST_REPORTS/chrome" yarn e2e --env chrome-headless
# Will exit with status of last command.
exit $?
echo Testing locally
yarn e2e --reports-folder "$REPORTS_FOLDER" --retries "$E2E_MAX_RETRIES" --headless
fi
Executable
+207
View File
@@ -0,0 +1,207 @@
#!/usr/bin/env node
process.env['NODE_ENV'] = 'test';
const browserstack = require('browserstack-local');
const {onshutdown, shutdown} = require('../bin/util');
const program = require('commander');
const Table = require('cli-table');
const serve = require('../serve');
const childProcess = require('child_process');
const uuid = require('uuid').v4;
const mongoose = require('../services/mongoose');
// Make things colorful!
require('colors');
function startTunnel(key, localIdentifier) {
const bs_local = new browserstack.Local();
// Code to start browserstack local before start of test
console.log('Connecting local');
return new Promise((resolve, reject) => {
bs_local.start({
key,
logFile: './test/e2e/bslocal.log',
verbose: 'true',
force: 'true',
onlyAutomate: 'true',
localIdentifier,
}, (error) => {
if (error) {
reject(error);
}
resolve();
});
onshutdown([
() => bs_local.stop(function(){}),
]);
});
}
function seleniumInstall() {
return new Promise((resolve, reject) => {
try {
const nw = childProcess.spawn(
'./node_modules/.bin/selenium-standalone',
['install'],
{
stdio: 'inherit',
});
nw.on('close', (code) => {
code === 0 ? resolve() : reject();
});
}
catch (ex) {
reject(ex);
}
});
}
function nightwatch(env, config, reportsFolder, browserstack) {
return new Promise((resolve, reject) => {
try {
const nw = childProcess.spawn(
'./node_modules/.bin/nightwatch',
['--config', config, '--env', env],
{
env: Object.assign({}, process.env, {
'BROWSERSTACK_LOCAL_IDENTIFIER': browserstack.localIdentifier,
'BROWSERSTACK_KEY': browserstack.key,
'BROWSERSTACK_USER': browserstack.user,
'REPORTS_FOLDER': `${reportsFolder}/${env}`,
}),
stdio: 'inherit',
});
nw.on('close', (code) => {
code === 0 ? resolve() : reject();
});
}
catch (ex) {
reject(ex);
}
});
}
function printResults(browsers, succeeded, retries) {
let table = new Table({
head: [
'Browser'.cyan,
'Status'.cyan,
'Retries'.cyan,
]
});
for (let browser of browsers) {
const wasSuccessful = browser in succeeded;
table.push([
browser,
wasSuccessful ? 'success'.green : 'failed'.red,
wasSuccessful ? succeeded[browser] : retries,
]);
}
console.log(table.toString());
}
function printSection(txt) {
console.log('*****************************'.magenta);
console.log(`${'*'.magenta} ${txt.cyan}`);
console.log('*****************************'.magenta);
}
async function runBrowserTests(browsers, config, retries = 1, reportsFolder, browserstack) {
const succeeded = {};
for (let browser of browsers) {
for (let t = 0; t < retries + 1; t++) {
try {
printSection(`e2e test for ${browser} #${t}`);
await nightwatch(browser, config, reportsFolder, browserstack);
succeeded[browser] = t;
console.log(`\n==> Succeeded e2e for ${browser} #${t}\n`.green);
break;
}
catch (ex) {
if (ex) {
console.log('There was an error while starting the test runner:\n\n');
process.stderr.write(`${ex.stack}\n`);
}
console.log(`\n==> Failed e2e for ${browser} #${t}\n`.red);
}
}
}
printResults(browsers, succeeded, retries);
return Object.keys(succeeded).length === browsers.length;
}
async function start(program) {
const localIdentifier = uuid();
let browsers = program.browsers.split(',');
const retries = Number.parseInt(program.retries);
const browserstack = {};
const date = new Date().toISOString()
.replace(/[T.]/g, '-')
.replace(/:/g, '');
const reportsFolder = `${program.reportsFolder}/${date}`;
let exitCode = 0;
let config = 'nightwatch.conf.js';
try {
if (program.tunnel && program.bsKey) {
await startTunnel(program.bsKey, localIdentifier);
}
console.log('Dropping test database');
await mongoose.connection.dropDatabase();
await serve();
if (program.bsKey) {
config = 'nightwatch-browserstack.conf.js';
browserstack.localIdentifier = localIdentifier;
browserstack.key = program.bsKey;
browserstack.user = program.bsUser;
} else {
// Install selenium standalone.
await seleniumInstall();
if (program.headless) {
browsers = browsers.map((b) => `${b}-headless`);
}
}
const succeeded = await runBrowserTests(
browsers,
config,
retries,
reportsFolder,
browserstack,
);
if (!succeeded) {
exitCode = 1;
}
}
catch (ex) {
console.log('There was an error:\n\n');
process.stderr.write(`${ex.stack}\n`);
process.exit(2);
}
finally {
console.log('Shutting down');
shutdown();
}
process.exit(exitCode);
}
program
.version('0.1.0')
.description('Perform e2e testing locally or if browserstack credentials are provided on browserstack.')
.option('-u, --bs-user [user]', 'Browserstack user', 'coralproject2')
.option('-k, --bs-key [key]', 'Browserstack api key')
.option('--no-tunnel', 'Dont start browserstack-local')
.option('-b, --browsers [list of browsers]', 'Browsers to test', 'chrome')
.option('-r, --retries [number]', 'Number of retries before failing', '1')
.option('--headless', 'Start in headless mode for local e2e')
.option('--reports-folder [folder]', 'Reports folder', './test/e2e/reports')
.parse(process.argv);
start(program);
+1 -2
View File
@@ -1,11 +1,10 @@
const serve = require('../../serve');
const mongoose = require('../../services/mongoose');
const {shutdown} = require('../../bin/util');
module.exports = {
before: async (done) => {
console.log('Dropping test database');
await mongoose.connection.dropDatabase();
await serve();
done();
},
after: (done) => {
+159 -17
View File
@@ -7,23 +7,165 @@ module.exports = {
return this
.waitForElementVisible('body');
},
openDrawer() {
this
.waitForElementVisible('@drawerButton')
.click('@drawerButton');
this.expect.section('@drawer').to.be.visible;
return this.section.drawer;
},
goToModerate() {
this
.click('@moderateNav')
.expect.section('@moderate').to.be.visible;
return this.section.moderate;
},
goToStories() {
this
.click('@storiesNav')
.expect.section('@stories').to.be.visible;
return this.section.stories;
},
goToCommunity() {
this
.click('@communityNav')
.expect.section('@community').to.be.visible;
return this.section.community;
},
logout() {
this
.waitForElementVisible('@settingsButton')
.click('@settingsButton')
.waitForElementVisible('@signOutButton')
.click('@signOutButton');
},
navigateAndLogin(user) {
this
.navigate()
.expect.section('@login').to.be.visible;
return this.section.login.login(user);
},
}],
elements: {
'loginLayout': '.talk-admin-login',
'signInForm': '.talk-admin-login-sign-in',
'emailInput': '.talk-admin-login-sign-in #email',
'passwordInput': '.talk-admin-login-sign-in #password',
'signInButton': '.talk-admin-login-sign-in-button',
'storiesNav': '.talk-admin-nav-stories',
'storiesDrawerNav': '.talk-admin-drawer-nav .talk-admin-nav-stories',
'storiesSection': '.talk-admin-stories',
'communityNav': '.talk-admin-nav-community',
'communityDrawerNav': '.talk-admin-drawer-nav .talk-admin-nav-community',
'communitySection': '.talk-admin-community',
'moderationContainer': '.talk-admin-moderation-container',
'drawerButton': '.mdl-layout__drawer-button',
'drawerOverlay': 'div.mdl-layout__obfuscator.is-visible',
'settingsButton': '.talk-admin-header-settings-button',
'signOutButton': '.talk-admin-header-sign-out',
}
drawerButton: '.mdl-layout__drawer-button',
drawerOverlay: 'div.mdl-layout__obfuscator.is-visible',
storiesNav: '.talk-admin-nav-stories',
communityNav: '.talk-admin-nav-community',
moderateNav: '.talk-admin-nav-moderate',
settingsButton: '.talk-admin-header-settings-button',
signOutButton: '.talk-admin-header-sign-out',
suspendUserDialog: '.talk-admin-suspend-user-dialog',
suspendUserConfirmButton: '.talk-admin-suspend-user-dialog-confirm',
supendUserSendButton: '.talk-admin-suspend-user-dialog-send',
toast: '.toastify',
toastClose: '.toastify__close',
},
sections: {
moderate: {
selector: '.talk-admin-moderation-container',
elements: {
comment: '.talk-admin-moderate-comment',
commentActionMenu: '.talk-admin-moderate-comment-actions-menu',
actionItemSuspendUser: '.action-menu-item#suspendUser',
actionMenuButton: '.talk-admin-moderate-comment-actions-menu #actions-dropdown-0'
}
},
stories: {
selector: '.talk-admin-stories',
},
community: {
selector: '.talk-admin-community',
commands: [{
url: function() {
return `${this.api.launchUrl}/admin/community`;
},
ready() {
return this
.waitForElementVisible('body');
},
goToPeople() {
this
.click('@peopleNav')
.expect.section('@people').to.be.visible;
return this.section.people;
},
}],
elements: {
peopleNav: '.talk-admin-nav-people',
flaggedAccountsNav: '.talk-admin-nav-flagged-accounts',
flaggedAccountsContainer: '.talk-adnin-community-flagged-accounts',
flaggedUser:'.talk-admin-community-flagged-user',
flaggedUserApproveButton: '.talk-admin-flagged-user-approve-button',
flaggedUserRejectButton: '.talk-admin-flagged-user-reject-button',
usernameDialog: '.talk-reject-username-dialog',
usernameDialogButtons: '.talk-reject-username-dialog-buttons',
usernameDialogSuspend: '.talk-reject-username-dialog-button-k',
usernameDialogSuspensionMessage: '.talk-reject-username-dialog-suspension-message'
},
sections: {
people: {
selector: '.talk-admin-community-people-container',
elements: {
firstRow: '.talk-admin-community-people-row:first-child',
dropdownStatus: '.talk-admin-community-people-dd-status',
dropdownRole: '.talk-admin-community-people-dd-role',
dropdownStatusActive: '.talk-admin-community-people-dd-status .dd-list-active',
optionActive: '.dd-option#ACTIVE',
optionBanned: '.dd-option#BANNED',
}
}
}
},
drawer: {
selector: '.talk-admin-drawer-nav',
commands: [{
goToStories() {
this
.click('@storiesButton');
this.parent.expect.section('@stories').to.be.visible;
this.close();
return this.parent.section.stories;
},
goToCommunity() {
this
.click('@communityButton');
this.parent.expect.section('@community').to.be.visible;
this.close();
return this.parent.section.stories;
},
close() {
this.parent
.click('@drawerOverlay')
.waitForElementNotPresent('@drawerOverlay');
return this.parent;
},
}],
elements: {
'storiesButton': '.talk-admin-drawer-nav .talk-admin-nav-stories',
'communityButton': '.talk-admin-drawer-nav .talk-admin-nav-community',
},
},
login: {
commands: [{
login(user) {
this
.waitForElementVisible('@signInForm')
.setValue('@emailInput', user.email)
.setValue('@passwordInput', user.password)
.waitForElementVisible('@signInButton')
.click('@signInButton');
const adminPage = this.api.page.admin();
adminPage.expect.section('@moderate').to.be.visible;
return adminPage.section.moderate;
},
}],
selector: '.talk-admin-login',
elements: {
'signInForm': '.talk-admin-login-sign-in',
'emailInput': '.talk-admin-login-sign-in #email',
'passwordInput': '.talk-admin-login-sign-in #password',
'signInButton': '.talk-admin-login-sign-in-button',
}
},
},
};
-22
View File
@@ -1,22 +0,0 @@
module.exports = {
commands: [{
url: function() {
return `${this.api.launchUrl}/admin/community`;
},
ready() {
return this
.waitForElementVisible('body');
},
}],
elements: {
container: '.talk-admin-community',
flaggedAccountsContainer: '.talk-adnin-community-flagged-accounts',
flaggedUser:'.talk-admin-community-flagged-user',
flaggedUserApproveButton: '.talk-admin-flagged-user-approve-button',
flaggedUserRejectButton: '.talk-admin-flagged-user-reject-button',
usernameDialog: '.talk-reject-username-dialog',
usernameDialogButtons: '.talk-reject-username-dialog-buttons',
usernameDialogSuspend: '.talk-reject-username-dialog-button-k',
usernameDialogSuspensionMessage: '.talk-reject-username-dialog-suspension-message'
}
};
+90 -29
View File
@@ -1,21 +1,38 @@
const iframeId = 'coralStreamEmbed_iframe';
const SortedWindowHandler = require('../utils/SortedWindowHandler');
module.exports = {
commands: [{
ready: function() {
this.switchToIframe();
this.expect.section('@comments').to.be.visible;
return this.section.comments;
},
goToProfileSection: function() {
this.waitForElementVisible('@profileTabButton');
this.click('@profileTabButton');
this.expect.section('@profile').to.be.visible;
return this.section.profile;
},
goToCommentsSection: function() {
this.waitForElementVisible('@commentsTabButton');
this.click('@commentsTabButton');
this.expect.section('@comments').to.be.visible;
return this.section.comments;
},
navigateToAsset: function(asset) {
this.api.url(`${this.api.launchUrl}/assets/title/${asset}`);
return this;
},
getEmbedSection: function() {
switchToIframe: function() {
this.waitForElementVisible('@iframe');
// Pause a bit to let iframe initialize in the hope that it'll
// fix https://www.browserstack.com/automate/builds/96419cf46e3d6376a36ae6d3f90934112df1ed91/sessions/224f1a1566c1c8c7859e2e76ece51862200f0173#automate_button
this.api.pause(200);
this.api.pause(1000);
this.api.frame(iframeId);
this.expect.section('@embed').to.be.present;
return this.section.embed;
return this;
},
}],
url: function() {
@@ -23,28 +40,66 @@ module.exports = {
},
elements: {
iframe: `#${iframeId}`,
commentsTabButton: '.talk-embed-stream-comments-tab > button',
profileTabButton: '.talk-embed-stream-profile-tab > button',
banDialog: '.talk-ban-user-dialog',
banDialogConfirmButton: '.talk-ban-user-dialog-button-confirm',
},
sections: {
embed: {
comments: {
commands: [{
getProfileSection: function() {
this.waitForElementVisible('@profileTabButton');
this.click('@profileTabButton');
this.expect.section('@profile').to.be.present;
return this.section.profile;
openLoginPopup(callback) {
const windowHandler = new SortedWindowHandler(this.api);
this
.waitForElementVisible('@signInButton')
.click('@signInButton');
// Wait for window to be created
// https://www.browserstack.com/automate/builds/1ceccf4efb4683b7feb890f45a32b5922b40ed3f/sessions/17b1a79682bef2498cb0be86eac317a08c976b0a#automate_button
this.api.pause(200);
// Focusing on the Login PopUp
windowHandler.windowHandles((handles) => {
this.api.switchWindow(handles[1]);
});
const popup = this.api.page.popup().ready();
callback(popup);
// Give a tiny bit of time to let popup close.
this.api.pause(50);
if (this.api.capabilities.browserName === 'MicrosoftEdge') {
// More time for edge.
// https://www.browserstack.com/automate/builds/1ceccf4efb4683b7feb890f45a32b5922b40ed3f/sessions/7393dbfda8387e43b6d5851f359b0c07db414973
this.api.pause(1000);
}
// Focusing on the Embed Window
windowHandler.windowHandles((handles) => {
this.api.switchWindow(handles[0]);
// For some reasons firefox does not automatically load auth after login.
// https://www.browserstack.com/automate/builds/37650cb4e66c6edce0ba0800a1c1b7e7f74bf991/sessions/7a4e9da69b0f9ecdf8b7fa9150639e47b1532cb0#automate_button
if (this.api.capabilities.browserName === 'firefox') {
this.parent.navigate().ready();
} else {
this.parent.switchToIframe();
}
});
return this;
},
getCommentsSection: function() {
this.waitForElementVisible('@commentsTabButton');
this.click('@commentsTabButton');
this.expect.section('@comments').to.be.present;
return this.section.comments;
logout() {
this
.waitForElementVisible('@logoutButton')
.click('@logoutButton');
},
}],
selector: '#talk-embed-stream-container',
selector: '.talk-embed-stream-comments-tab-pane',
elements: {
logoutButton: '.talk-stream-userbox-logout',
commentsTabButton: '.talk-embed-stream-comments-tab > button',
profileTabButton: '.talk-embed-stream-profile-tab > button',
signInButton: '#coralSignInButton',
commentBoxTextarea: '#commentText',
commentBoxPostButton: '.talk-plugin-commentbox-button',
@@ -62,24 +117,30 @@ module.exports = {
elements: {
offensiveUsernameRadio: '.talk-plugin-flags-popup-radio#USERNAME_OFFENSIVE',
flagUsernameRadio: '.talk-plugin-flags-popup-radio#USERS',
flagCommentRadio: '.talk-plugin-flags-popup-radio#COMMENTS',
continueButton: '.talk-plugin-flags-popup-button',
popUpText: '.talk-plugin-flags-popup-text',
spamCommentRadio: '.talk-plugin-flags-popup-radio#COMMENT_SPAM',
}
},
profile: {
selector: '.talk-embed-stream-profile-tab-pane',
mod: {
selector: '.talk-plugin-moderation-actions',
elements: {
notLoggedIn: '.talk-embed-stream-not-logged-in',
myCommentHistory: '.talk-my-profile-comment-history',
myCommentHistoryReactions: '.talk-my-profile-comment-history .comment-summary .comment-summary-reactions',
myCommentHistoryReactionCount: '.talk-my-profile-comment-history .comment-summary .comment-summary-reactions .comment-summary-reaction-count',
myCommentHistoryComment: '.talk-my-profile-comment-history .my-comment-body',
arrow: '.talk-plugin-moderation-actions-arrow',
menu: '.talk-plugin-modetarion-actions-menu',
banButton: '.talk-plugin-moderation-actions-ban',
},
},
comments: {
selector: '.talk-embed-stream-comments-tab-pane',
elements: {},
},
},
},
profile: {
selector: '.talk-embed-stream-profile-tab-pane',
elements: {
notLoggedIn: '.talk-embed-stream-not-logged-in',
myCommentHistory: '.talk-my-profile-comment-history',
myCommentHistoryReactions: '.talk-my-profile-comment-history .comment-summary .comment-summary-reactions',
myCommentHistoryReactionCount: '.talk-my-profile-comment-history .comment-summary .comment-summary-reactions .comment-summary-reaction-count',
myCommentHistoryComment: '.talk-my-profile-comment-history .my-comment-body',
},
},
},
-13
View File
@@ -1,13 +0,0 @@
module.exports = {
elements: {
registerButton: '#coralRegister',
signInButton: '#coralSignInButton',
emailInput: '#email',
usernameInput: '#username',
passwordInput: '#password',
confirmPasswordInput: '#confirmPassword',
signUpButton: '#coralSignUpButton',
signIn: '.coral-sign-in',
loginButton: '#coralLogInButton'
},
};
+41
View File
@@ -0,0 +1,41 @@
module.exports = {
commands: [{
ready() {
return this
.waitForElementVisible('body');
},
login(user) {
return this
.setValue('@emailInput', user.email)
.setValue('@passwordInput', user.password)
.waitForElementVisible('@signIn')
.waitForElementVisible('@loginButton')
.click('@loginButton');
},
register(user) {
return this
.waitForElementVisible('@registerButton')
.click('@registerButton')
.setValue('@emailInput', user.email)
.setValue('@usernameInput', user.username)
.setValue('@passwordInput', user.password)
.setValue('@confirmPasswordInput', user.password)
.waitForElementVisible('@signUpButton')
.click('@signUpButton')
.waitForElementVisible('@signIn')
.waitForElementVisible('@loginButton')
.click('@loginButton');
},
}],
elements: {
registerButton: '#coralRegister',
signInButton: '#coralSignInButton',
emailInput: '#email',
usernameInput: '#username',
passwordInput: '#password',
confirmPasswordInput: '#confirmPassword',
signUpButton: '#coralSignUpButton',
signIn: '.coral-sign-in',
loginButton: '#coralLogInButton'
},
};
+20 -8
View File
@@ -1,12 +1,27 @@
module.exports = {
'@tags': ['install'],
before: (client) => {
client.resizeWindow(1600, 1200);
},
afterEach: (client, done) => {
if (client.currentTest.results.failed) {
throw new Error('Test Case failed, skipping all the rest');
}
done();
},
after: (client) => {
client.end();
},
'User goes to install': (client) => {
const install = client.page.install();
install
.navigate()
.expect.section('@step1').to.be.present;
.expect.section('@step1').to.be.visible;
},
'User clicks get started button': (client) => {
@@ -20,7 +35,7 @@ module.exports = {
const install = client.page.install();
install
.expect.section('@step2').to.be.present;
.expect.section('@step2').to.be.visible;
},
'User fills step 2': (client) => {
const step2 = client.page.install().section.step2;
@@ -36,7 +51,7 @@ module.exports = {
const install = client.page.install();
install
.expect.section('@step3').to.be.present;
.expect.section('@step3').to.be.visible;
},
'User fills step 3': (client) => {
const step3 = client.page.install().section.step3;
@@ -54,7 +69,7 @@ module.exports = {
const install = client.page.install();
install
.expect.section('@step4').to.be.present;
.expect.section('@step4').to.be.visible;
},
'User fills step 4': (client) => {
const step4 = client.page.install().section.step4;
@@ -73,9 +88,6 @@ module.exports = {
const install = client.page.install();
install
.expect.section('@step5').to.be.present;
.expect.section('@step5').to.be.visible;
},
after: (client) => {
client.end();
}
};
+18 -33
View File
@@ -1,56 +1,41 @@
module.exports = {
'@tags': ['admin', 'login'],
beforeEach: (client) => {
before: (client) => {
client.resizeWindow(1024, 800);
},
afterEach: (client, done) => {
if (client.currentTest.results.failed) {
throw new Error('Test Case failed, skipping all the rest');
}
done();
},
after: (client) => {
client.end();
},
'Admin logs in': (client) => {
const adminPage = client.page.admin();
const {testData: {admin}} = client.globals;
adminPage
.navigate()
.waitForElementVisible('@loginLayout')
.waitForElementVisible('@signInForm')
.setValue('@emailInput', admin.email)
.setValue('@passwordInput', admin.password)
.waitForElementVisible('@signInButton')
.click('@signInButton');
adminPage
.waitForElementVisible('@moderationContainer');
adminPage.navigateAndLogin(admin);
},
'Admin goes to Stories': (client) => {
const adminPage = client.page.admin();
adminPage
.navigate()
.waitForElementVisible('@drawerButton')
.click('@drawerButton')
.waitForElementVisible('@storiesDrawerNav')
.click('@storiesDrawerNav')
.waitForElementVisible('@drawerOverlay')
.click('@drawerOverlay')
.waitForElementVisible('@storiesSection');
.openDrawer()
.goToStories();
},
'Admin goes to Community': (client) => {
const adminPage = client.page.admin();
adminPage
.navigate()
.waitForElementVisible('@drawerButton')
.click('@drawerButton')
.waitForElementVisible('@communityDrawerNav')
.click('@communityDrawerNav')
.waitForElementVisible('@drawerOverlay')
.click('@drawerOverlay')
.waitForElementVisible('@communitySection');
.openDrawer()
.goToCommunity();
},
after: (client) => {
client.end();
}
};
+37 -99
View File
@@ -1,7 +1,21 @@
const SortedWindowHandler = require('../utils/SortedWindowHandler');
module.exports = {
'@tags': ['embedStream', 'login'],
before: (client) => {
client.resizeWindow(1600, 1200);
},
afterEach: (client, done) => {
if (client.currentTest.results.failed) {
throw new Error('Test Case failed, skipping all the rest');
}
done();
},
after: (client) => {
client.end();
},
'creates a new asset': (client) => {
const asset = 'newAssetTest';
const embedStream = client.page.embedStream();
@@ -9,92 +23,43 @@ module.exports = {
embedStream
.navigateToAsset(asset)
.assert.title(asset)
.getEmbedSection();
.ready();
},
'creates an user and user logs in': (client) => {
const {testData: {user}} = client.globals;
const embedStream = client.page.embedStream();
const embed = embedStream
// Go back to default asset.
const comments =
embedStream
.navigate()
.getEmbedSection();
.ready();
const windowHandler = new SortedWindowHandler(client);
embed
.waitForElementVisible('@signInButton')
.click('@signInButton');
// Wait for window to be created
// https://www.browserstack.com/automate/builds/1ceccf4efb4683b7feb890f45a32b5922b40ed3f/sessions/17b1a79682bef2498cb0be86eac317a08c976b0a#automate_button
client.pause(200);
// Focusing on the Login PopUp
windowHandler.windowHandles((handles) => {
client.switchWindow(handles[1]);
});
const login = client.page.login();
login
.waitForElementVisible('@registerButton')
.click('@registerButton')
.setValue('@emailInput', user.email)
.setValue('@usernameInput', user.username)
.setValue('@passwordInput', user.password)
.setValue('@confirmPasswordInput', user.password)
.waitForElementVisible('@signUpButton')
.click('@signUpButton')
.waitForElementVisible('@signIn')
.waitForElementVisible('@loginButton')
.click('@loginButton');
// Give a tiny bit of time to let popup close.
client.pause(50);
if (client.capabilities.browserName === 'MicrosoftEdge') {
// More time for edge.
// https://www.browserstack.com/automate/builds/1ceccf4efb4683b7feb890f45a32b5922b40ed3f/sessions/7393dbfda8387e43b6d5851f359b0c07db414973
client.pause(1000);
}
// Focusing on the Embed Window
windowHandler.windowHandles((handles) => {
client.switchWindow(handles[0]);
});
comments
.openLoginPopup((popup) => {
popup.register(user);
});
},
'user posts a comment': (client) => {
const embedStream = client.page.embedStream();
const comments = client.page.embedStream().section.comments;
const {testData: {comment}} = client.globals;
const embed = embedStream
.navigate()
.getEmbedSection();
embed
comments
.waitForElementVisible('@commentBoxTextarea')
.setValue('@commentBoxTextarea', comment.body)
.waitForElementVisible('@commentBoxPostButton')
.click('@commentBoxPostButton')
.waitForElementVisible('@firstCommentContent')
.getText('@firstCommentContent', (result) => {
embed.assert.equal(result.value, comment.body);
comments.assert.equal(result.value, comment.body);
});
},
'signed in user sees comment history': (client) => {
const embedStream = client.page.embedStream();
const profile = client.page.embedStream().goToProfileSection();
const {testData: {comment}} = client.globals;
const embed = embedStream
.navigate()
.getEmbedSection();
const profile = embed
.getProfileSection();
profile
.waitForElementVisible('@myCommentHistory')
.waitForElementVisible('@myCommentHistoryComment')
@@ -103,14 +68,7 @@ module.exports = {
});
},
'user sees replies and reactions to comments': (client) => {
const embedStream = client.page.embedStream();
const embed = embedStream
.navigate()
.getEmbedSection();
const profile = embed
.getProfileSection();
const profile = client.page.embedStream().section.profile;
profile
.waitForElementVisible('@myCommentHistory')
@@ -122,17 +80,12 @@ module.exports = {
},
'user goes to the stream and replies and reacts to comment': (client) => {
const embedStream = client.page.embedStream();
const embed = embedStream
.navigate()
.getEmbedSection();
embed
const comments = embedStream.goToCommentsSection();
comments
.waitForElementVisible('@respectButton')
.click('@respectButton');
const profile = embed
.getProfileSection();
const profile = embedStream.goToProfileSection();
profile
.waitForElementVisible('@myCommentHistory')
@@ -144,31 +97,16 @@ module.exports = {
},
'user logs out': (client) => {
const embedStream = client.page.embedStream();
const comments = embedStream.goToCommentsSection();
const embed = embedStream
.navigate()
.getEmbedSection();
embed
.waitForElementVisible('@commentsTabButton')
.click('@commentsTabButton')
.waitForElementVisible('@logoutButton')
.click('@logoutButton');
comments
.logout();
},
'not logged in user clicks my profile tab': (client) => {
const embedStream = client.page.embedStream();
const embed = embedStream
.navigate()
.getEmbedSection();
const profile = embed
.getProfileSection();
const profile = embedStream.goToProfileSection();
profile
.assert.visible('@notLoggedIn');
},
after: (client) => {
client.end();
}
};
+45 -85
View File
@@ -1,36 +1,39 @@
module.exports = {
before: (client) => {
client.resizeWindow(1600, 1200);
},
afterEach: (client, done) => {
if (client.currentTest.results.failed) {
throw new Error('Test Case failed, skipping all the rest');
}
done();
},
after: (client) => {
client.end();
},
'admin logs in': (client) => {
const adminPage = client.page.admin();
const {testData: {admin}} = client.globals;
adminPage
.navigate()
.waitForElementVisible('@loginLayout')
.waitForElementVisible('@signInForm')
.setValue('@emailInput', admin.email)
.setValue('@passwordInput', admin.password)
.waitForElementVisible('@signInButton')
.click('@signInButton');
client.pause(3000);
adminPage
.waitForElementVisible('@moderationContainer');
adminPage.navigateAndLogin(admin);
},
'admin flags user\'s username as offensive': (client) => {
const embedStream = client.page.embedStream();
const flagSection = client.page.embedStream().section.embed.section.flag;
const embed = embedStream
const comments = embedStream
.navigate()
.getEmbedSection();
.ready();
embed
comments
.waitForElementVisible('@firstComment')
.waitForElementVisible('@flagButton')
.click('@flagButton');
flagSection
comments.section.flag
.waitForElementVisible('@flagUsernameRadio')
.click('@flagUsernameRadio')
.waitForElementVisible('@continueButton')
@@ -42,26 +45,27 @@ module.exports = {
.click('@continueButton');
},
'admin goes to Reported Usernames': (client) => {
const community = client.page.adminCommunity();
const adminPage = client.page.admin();
const community = adminPage
.navigate()
.ready()
.goToCommunity();
community
.navigate();
community
.waitForElementVisible('@container')
.waitForElementVisible('@flaggedAccountsContainer')
.waitForElementVisible('@flaggedUser');
},
'admin rejects the user flag': (client) => {
const community = client.page.adminCommunity();
const community = client.page.admin().section.community;
community
.waitForElementVisible('@flaggedUserRejectButton')
.click('@flaggedUserRejectButton');
},
'admin suspends the user': (client) => {
const community = client.page.adminCommunity();
const community = client.page.admin().section.community;
community
.waitForElementVisible('@usernameDialog')
.waitForElementVisible('@usernameDialogButtons')
@@ -72,86 +76,42 @@ module.exports = {
.waitForElementNotPresent('@flaggedUser');
},
'admin logs out': (client) => {
const admin = client.page.admin();
admin
.waitForElementVisible('@settingsButton')
.click('@settingsButton')
.waitForElementVisible('@signOutButton')
.click('@signOutButton');
client.page.admin().logout();
},
'user logs in': (client) => {
const {testData: {user}} = client.globals;
const embedStream = client.page.embedStream();
const embed = embedStream
embedStream
.navigate()
.getEmbedSection();
embed
.waitForElementVisible('@signInButton')
.click('@signInButton');
client.pause(3000);
// Focusing on the Login PopUp
client.windowHandles((result) => {
const handle = result.value[1];
client.switchWindow(handle);
});
const login = client.page.login();
login
.setValue('@emailInput', user.email)
.setValue('@passwordInput', user.password)
.waitForElementVisible('@signIn')
.waitForElementVisible('@loginButton')
.click('@loginButton');
// Focusing on the Embed Window
client.windowHandles((result) => {
const handle = result.value[0];
client.switchWindow(handle);
});
.ready()
.openLoginPopup((popup) => popup.login(user));
},
'user account is suspended, should see restricted message box': (client) => {
const embedStream = client.page.embedStream();
const comments = embedStream.section.comments;
const embed = embedStream
.navigate()
.getEmbedSection();
embed
comments
.waitForElementVisible('@restrictedMessageBox');
},
'user picks another username': (client) => {
const {testData: {user}} = client.globals;
const embedStream = client.page.embedStream();
const embed = embedStream
.navigate()
.getEmbedSection();
const comments = embedStream.section.comments;
const {testData: {user}} = client.globals;
embed
comments
.waitForElementVisible('@suspendedAccountInput')
.setValue('@suspendedAccountInput', `${user.username}-alternative`)
.setValue('@suspendedAccountInput', `${user.username}_alternative`)
.waitForElementVisible('@suspendedAccountSubmitButton')
.click('@suspendedAccountSubmitButton');
.click('@suspendedAccountSubmitButton')
.waitForElementNotPresent('@suspendedAccountInput');
},
'user should not be able to comment': (client) => {
const embedStream = client.page.embedStream();
const embed = embedStream
.navigate()
.getEmbedSection();
const comments = embedStream.section.comments;
embed
comments
.waitForElementNotPresent('@commentBoxTextarea')
.waitForElementNotPresent('@commentBoxPostButton');
},
after: (client) => {
client.end();
}
};
+125
View File
@@ -0,0 +1,125 @@
module.exports = {
before: (client) => {
client.resizeWindow(1600, 1200);
},
afterEach: (client, done) => {
if (client.currentTest.results.failed) {
throw new Error('Test Case failed, skipping all the rest');
}
done();
},
after: (client) => {
client.end();
},
'admin logs in': (client) => {
const adminPage = client.page.admin();
const {testData: {admin}} = client.globals;
adminPage.navigateAndLogin(admin);
},
'navigate to the embed stream': (client) => {
const embedStream = client.page.embedStream();
embedStream
.navigate()
.ready();
},
'admin bans user': (client) => {
const embedStream = client.page.embedStream();
const comments = embedStream.section.comments;
comments.section.mod
.waitForElementVisible('@arrow')
.click('@arrow')
.waitForElementVisible('@menu')
.waitForElementVisible('@banButton')
.click('@banButton');
embedStream
.waitForElementVisible('@banDialog')
.waitForElementVisible('@banDialogConfirmButton')
.click('@banDialogConfirmButton')
.waitForElementNotVisible('@banDialog');
},
'admin logs out': (client) => {
const comments = client.page.embedStream().section.comments;
comments
.logout();
},
'user logs in': (client) => {
const {testData: {user}} = client.globals;
const comments = client.page.embedStream().section.comments;
comments
.openLoginPopup((popup) => popup.login(user));
},
'user account is banned, should see restricted message box': (client) => {
const embedStream = client.page.embedStream();
const comments = embedStream.section.comments;
comments
.waitForElementVisible('@restrictedMessageBox');
},
'user logs out': (client) => {
const embedStream = client.page.embedStream();
const comments = embedStream.section.comments;
comments
.logout();
},
'admin logs in (2)': (client) => {
const adminPage = client.page.admin();
const {testData: {admin}} = client.globals;
adminPage.navigateAndLogin(admin);
},
'admin goes to community': (client) => {
const adminPage = client.page.admin();
adminPage
.goToCommunity()
.goToPeople();
},
'admin removes ban from user': (client) => {
const people = client.page.admin()
.section.community
.section.people;
people
.waitForElementVisible('@firstRow')
.waitForElementVisible('@dropdownStatus')
.click('@dropdownStatus')
.waitForElementVisible('@dropdownStatusActive')
.click('@optionActive');
},
'admin logs out 2': (client) => {
client.page.admin().logout();
},
'navigate to the embed stream 2': (client) => {
const embedStream = client.page.embedStream();
embedStream
.navigate()
.ready();
},
'user logs in 2': (client) => {
const {testData: {user}} = client.globals;
const comments = client.page.embedStream().section.comments;
comments
.openLoginPopup((popup) => popup.login(user));
},
'user should be able to comment': (client) => {
const embedStream = client.page.embedStream();
const comments = embedStream.section.comments;
comments
.waitForElementVisible('@commentBoxTextarea')
.waitForElementVisible('@commentBoxPostButton');
},
};
+138
View File
@@ -0,0 +1,138 @@
module.exports = {
before: (client) => {
client.resizeWindow(1600, 1200);
},
afterEach: (client, done) => {
if (client.currentTest.results.failed) {
throw new Error('Test Case failed, skipping all the rest');
}
done();
},
after: (client) => {
client.end();
},
'user logs in': (client) => {
const {testData: {user}} = client.globals;
const embedStream = client.page.embedStream();
const comments = client.page.embedStream().section.comments;
embedStream
.navigate()
.ready();
comments
.openLoginPopup((popup) => popup.login(user));
},
'user posts comment': (client) => {
const comments = client.page.embedStream().section.comments;
const {testData: {comment}} = client.globals;
comments
.waitForElementVisible('@commentBoxTextarea')
.setValue('@commentBoxTextarea', comment.body)
.waitForElementVisible('@commentBoxPostButton')
.click('@commentBoxPostButton')
.waitForElementVisible('@firstCommentContent')
.getText('@firstCommentContent', (result) => {
comments.assert.equal(result.value, comment.body);
});
},
'user logs out': (client) => {
const embedStream = client.page.embedStream();
const comments = embedStream.section.comments;
comments
.logout();
},
'admin logs in': (client) => {
const adminPage = client.page.admin();
const {testData: {admin}} = client.globals;
adminPage.navigateAndLogin(admin);
},
'navigate to the embed stream': (client) => {
const embedStream = client.page.embedStream();
embedStream
.navigate()
.ready();
},
'admin reports comment': (client) => {
const embedStream = client.page.embedStream();
const comments = embedStream.section.comments;
comments
.waitForElementVisible('@firstComment')
.waitForElementVisible('@flagButton')
.click('@flagButton');
comments.section.flag
.waitForElementVisible('@flagCommentRadio')
.click('@flagCommentRadio')
.waitForElementVisible('@continueButton')
.click('@continueButton')
.waitForElementVisible('@spamCommentRadio')
.click('@spamCommentRadio')
.click('@continueButton')
.waitForElementVisible('@popUpText')
.click('@continueButton');
},
'admin suspends user': (client) => {
const adminPage = client.page.admin();
const moderate = adminPage.section.moderate;
adminPage
.navigate()
.ready()
.goToModerate();
moderate
.waitForElementVisible('@comment')
.waitForElementVisible('@commentActionMenu')
.waitForElementVisible('@actionMenuButton')
.click('@actionMenuButton')
.waitForElementVisible('@actionItemSuspendUser')
.click('@actionItemSuspendUser');
adminPage
.waitForElementVisible('@suspendUserDialog')
.waitForElementVisible('@suspendUserConfirmButton')
.click('@suspendUserConfirmButton')
.waitForElementVisible('@supendUserSendButton')
.click('@supendUserSendButton');
adminPage
.waitForElementVisible('@toast')
.waitForElementVisible('@toastClose')
.click('@toastClose');
},
'admin logs out': (client) => {
const adminPage = client.page.admin();
adminPage
.logout();
},
'user logs in (2)': (client) => {
const {testData: {user}} = client.globals;
const embedStream = client.page.embedStream();
const comments = client.page.embedStream().section.comments;
embedStream
.navigate()
.ready();
comments
.openLoginPopup((popup) => popup.login(user));
},
'user account is suspended, should see restricted message box': (client) => {
const embedStream = client.page.embedStream();
const comments = embedStream.section.comments;
comments
.waitForElementVisible('@restrictedMessageBox');
},
};