Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

e2e tests: improve reliability of login test #1913

Merged
merged 8 commits into from
Feb 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 108 additions & 18 deletions e2e/test.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,136 @@
import 'regenerator-runtime/runtime';
import path from 'path';
import rimraf from 'rimraf';
import randomString from '../lib/utils/crypto-random-string';
import { Application } from 'spectron';

const TEST_USERNAME = `sptest-${randomString(16)}@test.localhost.localdomain`;
const TEST_PASSWORD = randomString(22);
console.info(
`Creating user:\n email: ${TEST_USERNAME}\n password: ${TEST_PASSWORD}`
);

const el = (app: Application, selector: string) => app.client.$(selector);
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const waitFor = async (app: Application, selector: string, msTimeout = 10000) =>
expect(await app.client.waitForExist(selector, msTimeout)).toBe(true);
const waitForEvent = async (
app: Application,
eventName: string,
msTimeout = 10000
): Promise<any[]> => {
const tic = Date.now();

return new Promise((resolve, reject) => {
const f = async () => {
const result = await app.client.execute(function() {
var events = window.testEvents;

if (!events.length) {
return undefined;
}

window.testEvents = [];
return events;
});

const firstOfType =
result.value &&
result.value.findIndex(
(event: string | [string, ...any[]]) =>
event === eventName || event[0] === eventName
);

if (result.value && firstOfType > -1) {
resolve(
'string' === typeof result.value
? []
: result.value[firstOfType].slice(1)
);
} else if (Date.now() - tic < msTimeout) {
setTimeout(f, 100);
} else {
reject();
}
};

f();
});
};

const app: Application = new Application({
path: path.join(__dirname, '../node_modules/.bin/electron'),
args: [path.join(__dirname, '..')],
});

let userData = '';

beforeAll(async () => {
await app.start();
const userData = await app.electron.remote.app.getPath('userData');
userData = await app.electron.remote.app.getPath('userData');
await app.stop();
await new Promise(resolve => rimraf(userData, () => resolve()));
await app.start();
}, 10000);

afterAll(async () => app && app.isRunning() && (await app.stop()));

describe('E2E', () => {
test('starts', async () => {
beforeEach(async () => {
await new Promise(resolve => rimraf(userData, () => resolve()));
await app.start();
await app.client.waitUntilWindowLoaded();
}, 10000);

afterEach(async () => app && app.isRunning() && (await app.stop()));

test('starts', async () => {
expect(app.isRunning()).toEqual(true);
});

test('logs in', async () => {
await app.client.waitUntilWindowLoaded();
const usernameField = '#login__field-username';
const passwordField = '#login__field-password';
const loginButton = '#login__login-button';

app.client
.$('#login__field-username')
.setValue(process.env.TEST_USERNAME as string);
app.client
.$('#login__field-password')
.setValue(process.env.TEST_PASSWORD as string);
const loginWith = async (username: string, password: string) => {
await waitFor(app, usernameField);
el(app, usernameField).setValue(username);
await waitFor(app, passwordField);
el(app, passwordField).setValue(password);

await wait(500);
await wait(2000); // try and prevent DDoS protection
await waitFor(app, loginButton);
el(app, loginButton).click();
};

app.client.$('#login__login-button').click();
test('creates an account', async () => {
await waitFor(app, '=Sign up');
el(app, '=Sign up').click();

await app.client.waitUntilWindowLoaded();
await app.client.waitForExist('.note-list', 10000);
await waitFor(app, usernameField);
await loginWith(TEST_USERNAME, TEST_PASSWORD);

await waitForEvent(app, 'notesLoaded');
await wait(1000); // @TODO: This delay is necessary but shouldn't be
}, 20000);

test('login with wrong password fails', async () => {
await loginWith(TEST_USERNAME, `${TEST_PASSWORD}_wrong`);

await waitFor(app, '[data-error-name="invalid-login"]');
}, 20000);

test('login with correct password logs in', async () => {
await loginWith(TEST_USERNAME, TEST_PASSWORD);

await waitForEvent(app, 'notesLoaded');
}, 20000);

test('can create new note by clicking on new note button', async () => {
await loginWith(TEST_USERNAME, TEST_PASSWORD);
await waitForEvent(app, 'notesLoaded');
await wait(1000); // @TODO: This delay is necessary but shouldn't be

const newNoteButton = 'button[data-title="New Note"]';
await waitFor(app, newNoteButton);
el(app, newNoteButton).click();

await waitForEvent(app, 'editorNewNote');
}, 20000);
});
4 changes: 4 additions & 0 deletions lib/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ export const App = connect(
);

this.toggleShortcuts(true);

__TEST__ && window.testEvents.push('booted');
}

componentWillUnmount() {
Expand Down Expand Up @@ -314,6 +316,8 @@ export const App = connect(

loadNotes({ noteBucket });
setUnsyncedNoteIds(getUnsyncedNoteIds(noteBucket));

__TEST__ && window.testEvents.push('notesLoaded');
};

onNoteRemoved = () => this.onNotesIndex();
Expand Down
12 changes: 10 additions & 2 deletions lib/auth/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,20 @@ export class Auth extends Component {
<h1>{buttonLabel}</h1>

{this.props.hasInvalidCredentials && (
<p className="login__auth-message is-error">
<p
className="login__auth-message is-error"
data-error-name="invalid-login"
>
Could not log in with the provided email address and password.
</p>
)}
{this.props.hasLoginError && (
<p className="login__auth-message is-error">{errorMessage}</p>
<p
className="login__auth-message is-error"
data-error-name="invalid-login"
>
{errorMessage}
</p>
)}
{passwordErrorMessage && (
<p className="login__auth-message is-error">
Expand Down
4 changes: 4 additions & 0 deletions lib/boot.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
if (__TEST__) {
window.testEvents = [];
}

import './utils/ensure-platform-support';
import 'core-js/stable';
import 'regenerator-runtime/runtime';
Expand Down
9 changes: 9 additions & 0 deletions lib/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
declare var __TEST__: boolean;

interface Window {
_tkq: Array;
testEvents: (string | [string, ...any[]])[];
wpcom: {
tracks: object;
};
}
2 changes: 1 addition & 1 deletion lib/icon-button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const IconButton = ({ icon, title, ...props }) => (
enterDelay={200}
title={title}
>
<button className="icon-button" type="button" {...props}>
<button className="icon-button" type="button" data-title={title} {...props}>
{icon}
</button>
</Tooltip>
Expand Down
14 changes: 11 additions & 3 deletions lib/note-content-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,17 @@ class NoteContentEditor extends Component<Props> {
noteId !== prevProps.noteId ||
content.version !== prevProps.content.version
) {
this.setState({
editorState: this.createNewEditorState(content.text, searchQuery),
});
this.setState(
{
editorState: this.createNewEditorState(content.text, searchQuery),
},
() =>
__TEST__ &&
window.testEvents.push([
'editorNewNote',
plainTextContent(this.state.editorState),
])
);
return;
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"scripts": {
"dev": "make dev",
"start": "make start NODE_ENV=development",
"test-e2e": "npx jest --config=./jest.config.e2e.js",
"test-e2e": "npx jest --config=./jest.config.e2e.js --runInBand",
"test": "make test",
"lint": "make lint",
"format": "make format",
Expand Down
1 change: 1 addition & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ module.exports = () => {
chunkFilename: isDevMode ? '[id].css' : '[id].[hash].css',
}),
new webpack.DefinePlugin({
__TEST__: JSON.stringify(process.env.NODE_ENV === 'test'),
config: JSON.stringify(config),
}),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
Expand Down