Skip to content

Commit 2eab77c

Browse files
[E2E] Add initial draft of end-to-end testing / UI Automation (mattermost#38)
* add e2e ui-automation * update initial files
1 parent d02d31b commit 2eab77c

19 files changed

+1059
-46
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ node_modules
44
*.swp
55
.idea
66
*.tar.gz
7+
tests/reports
8+
.DS_Store

nightwatch.conf.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
require('babel-core/register');
2+
3+
module.exports = require('./nightwatch.json');

nightwatch.json

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
{
2+
"src_folders": ["./tests/e2e/tests"],
3+
"output_folder": "./tests/reports",
4+
"custom_commands_path": "tests/e2e/commands",
5+
"custom_assertions_path": "",
6+
"page_objects_path": "tests/e2e/pages",
7+
"globals_path": "tests/e2e/globals.js",
8+
"selenium" : {
9+
"start_process" : false,
10+
"server_path" : "",
11+
"log_path" : "",
12+
"host" : "localhost",
13+
"port" : 4444,
14+
"cli_args" : {
15+
"webdriver.chrome.driver" : "",
16+
"webdriver.ie.driver" : "",
17+
"webdriver.gecko.driver": ""
18+
}
19+
},
20+
"test_settings": {
21+
"default": {
22+
"launch_url": "http://localhost:8065/",
23+
"selenium_port": 4444,
24+
"selenium_host": "localhost",
25+
"silent": true,
26+
"skip_testcases_on_fail": false,
27+
"end_session_on_fail": false,
28+
"resolution": "1920x1080",
29+
"exclude": "utils.js",
30+
"screenshots": {
31+
"enabled": true,
32+
"on_failure": true,
33+
"path": "./tests/reports/screenshots"
34+
},
35+
"desiredCapabilities": {
36+
"browserName": "firefox",
37+
"unexpectedAlertBehaviour": "dismiss",
38+
"javascriptEnabled": true,
39+
"acceptSslCerts": true,
40+
"elementScrollBehavior": "1"
41+
}
42+
},
43+
"chrome": {
44+
"desiredCapabilities": {
45+
"browserName": "chrome",
46+
"javascriptEnabled": true,
47+
"acceptSslCerts": true,
48+
"chromeOptions": {
49+
"args": ["window-position=0,0", "window-size=1920,1080", "--disable-popup-blocking"]
50+
}
51+
}
52+
},
53+
"firefox": {
54+
"desiredCapabilities": {
55+
"browserName": "firefox",
56+
"unexpectedAlertBehaviour": "dismiss",
57+
"javascriptEnabled": true,
58+
"acceptSslCerts": true,
59+
"elementScrollBehavior": "1"
60+
}
61+
},
62+
"360x640": {
63+
"resolution": "360x640",
64+
"desiredCapabilities": {
65+
"browserName": "chrome",
66+
"javascriptEnabled": true,
67+
"acceptSslCerts": true,
68+
"chromeOptions": {
69+
"args": ["window-position=0,0", "window-size=360,640"]
70+
}
71+
}
72+
},
73+
"768x1024": {
74+
"resolution": "768x1024",
75+
"desiredCapabilities": {
76+
"browserName": "chrome",
77+
"javascriptEnabled": true,
78+
"acceptSslCerts": true,
79+
"chromeOptions": {
80+
"args": ["window-position=0,0", "window-size=768,1024"]
81+
}
82+
}
83+
}
84+
}
85+
}

package.json

+9-1
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,14 @@
8383
"jsdom": "11.1.0",
8484
"jsdom-global": "3.0.2",
8585
"json-loader": "0.5.7",
86+
"nightwatch": "0.9.16",
8687
"node-sass": "4.5.3",
8788
"raw-loader": "0.5.1",
8889
"react-addons-test-utils": "15.6.0",
8990
"remote-redux-devtools": "0.5.12",
9091
"remote-redux-devtools-on-debugger": "0.8.2",
9192
"sass-loader": "6.0.6",
93+
"selenium-standalone": "6.6.0",
9294
"style-loader": "0.18.2",
9395
"url-loader": "0.5.9",
9496
"webpack": "3.5.5",
@@ -136,6 +138,12 @@
136138
"test": "jest",
137139
"updatesnapshot": "jest --updateSnapshot",
138140
"test:watch": "jest --watch",
139-
"test:coverage": "jest --coverage"
141+
"test:coverage": "jest --coverage",
142+
"e2e": "./tests/e2e/test.sh",
143+
"e2e-local-chrome": "nightwatch -e chrome --suiteRetries 1",
144+
"e2e-local-firefox": "nightwatch -e firefox --suiteRetries 1",
145+
"e2e-tag": "nightwatch -e chrome --suiteRetries 1 --tag",
146+
"selenium-install": "selenium-standalone install --config=./tests/e2e/config.js",
147+
"selenium-start": "selenium-standalone start --config=./tests/e2e/config.js"
140148
}
141149
}

tests/e2e/commands/fillInput.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
2+
// See License.txt for license information.
3+
4+
exports.command = function fillInput(element, string) {
5+
return this
6+
.waitForElementVisible(element, 3000)
7+
.clearValue(element)
8+
.pause(300)
9+
.setValue(element, string)
10+
.pause(1000);
11+
};

tests/e2e/commands/getChromeLogs.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
2+
// See License.txt for license information.
3+
4+
exports.command = function getChromeLogs() {
5+
return this.getLog('browser', (logEntriesArray) => {
6+
logEntriesArray.forEach((log) => {
7+
console.log(`[${log.level}] Timestamp: ${log.timestamp}\n`); //eslint-disable-line no-console
8+
});
9+
});
10+
};

tests/e2e/config.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
2+
// See License.txt for license information.
3+
4+
module.exports = {
5+
baseURL: "https://selenium-release.storage.googleapis.com",
6+
version: "3.4.0",
7+
drivers: {
8+
chrome: {
9+
version: "2.31",
10+
arch: process.arch,
11+
baseURL: "https://chromedriver.storage.googleapis.com"
12+
},
13+
ie: {
14+
version: '3.4.0',
15+
arch: process.arch,
16+
baseURL: 'https://selenium-release.storage.googleapis.com'
17+
},
18+
firefox: {
19+
version: '0.17.0',
20+
arch: process.arch,
21+
baseURL: 'https://github.com/mozilla/geckodriver/releases/download'
22+
},
23+
edge: {
24+
version: '15063'
25+
}
26+
}
27+
};

tests/e2e/globals.js

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
2+
// See License.txt for license information.
3+
4+
module.exports = {
5+
// this controls whether to abort the test execution when an assertion failed and skip the rest
6+
// it's being used in waitFor commands and expect assertions
7+
abortOnAssertionFailure: true,
8+
9+
// this will overwrite the default polling interval (currently 500ms) for waitFor commands
10+
// and expect assertions that use retry
11+
waitForConditionPollInterval: 300,
12+
13+
// default timeout value in milliseconds for waitFor commands and implicit waitFor value for
14+
// expect assertions
15+
waitForConditionTimeout: 5000,
16+
17+
// this will cause waitFor commands on elements to throw an error if multiple
18+
// elements are found using the given locate strategy and selector
19+
throwOnMultipleElementsReturned: true,
20+
21+
// controls the timeout time for async hooks. Expects the done() callback to be invoked within this time
22+
// or an error is thrown
23+
asyncHookTimeout: 10000,
24+
25+
default: {
26+
myGlobal: function() {
27+
return "";
28+
}
29+
},
30+
31+
test_env: {
32+
myGlobal: 'test_global',
33+
beforeEach: function() {}
34+
},
35+
36+
before: function(cb) {
37+
cb();
38+
},
39+
40+
beforeEach: function(browser, cb) {
41+
cb();
42+
},
43+
44+
after: function(cb) {
45+
cb();
46+
},
47+
48+
afterEach: function(browser, cb) {
49+
cb();
50+
},
51+
52+
reporter: function(results, cb) {
53+
cb();
54+
}
55+
};
+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
2+
// See License.txt for license information.
3+
4+
import {Constants} from '../utils';
5+
6+
const centerChannelHeaderCommands = {
7+
navigateToPage() {
8+
return this.waitForElementVisible('@headerContainer', Constants.DEFAULT_WAIT);
9+
}
10+
};
11+
12+
module.exports = {
13+
url: `${Constants.TEST_BASE_URL}`,
14+
commands: [centerChannelHeaderCommands],
15+
elements: {
16+
headerContainer: {
17+
selector: '//*[@id="channel-header"]',
18+
locateStrategy: 'xpath'
19+
},
20+
flexParent: {
21+
selector: '//*[@id="channel-header"]/div',
22+
locateStrategy: 'xpath'
23+
},
24+
headerInfo: {
25+
selector: '//*[@id="channel-header"]/div/div[1]/div',
26+
locateStrategy: 'xpath'
27+
},
28+
toggleFavorite: {
29+
selector: '//*[@id="toggleFavorite"]',
30+
locateStrategy: 'xpath'
31+
},
32+
dropdownButton: {
33+
selector: '//*[@id="channelHeaderDropdown"]',
34+
locateStrategy: 'xpath'
35+
},
36+
dropdownMenu: {
37+
selector: '//*[@id="channel-header"]/div/div[1]/div/div/ul',
38+
locateStrategy: 'xpath'
39+
},
40+
headerDescription: {
41+
selector: '//*[@id="channel-header"]/div/div[1]/div/a[2]',
42+
locateStrategy: 'xpath'
43+
},
44+
headerMember: {
45+
selector: '//*[@id="channel-header"]/div/div[3]/div',
46+
locateStrategy: 'xpath'
47+
},
48+
headerMemberText: {
49+
selector: '//*[@id="member_popover"]/span[1]',
50+
locateStrategy: 'xpath'
51+
},
52+
headerMemberIcon: {
53+
selector: '//*[@id="member_popover"]/span[2]',
54+
locateStrategy: 'xpath'
55+
},
56+
headerPin: {
57+
selector: '//*[@id="channel-header"]/div/div[4]/div',
58+
locateStrategy: 'xpath'
59+
},
60+
headerSearchBar: {
61+
selector: '//*[@id="channel-header"]/div/div[5]',
62+
locateStrategy: 'xpath'
63+
},
64+
headerAtMention: {
65+
selector: '//*[@id="channel-header"]/div/div[6]/div',
66+
locateStrategy: 'xpath'
67+
},
68+
headerFlag: {
69+
selector: '//*[@id="channel-header"]/div/div[7]/div',
70+
locateStrategy: 'xpath'
71+
}
72+
}
73+
};

tests/e2e/pages/centerPage.js

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
2+
// See License.txt for license information.
3+
4+
import {Constants} from '../utils';
5+
6+
const centerCommands = {
7+
navigateToPage() {
8+
return this.waitForElementVisible('@postTextBox', Constants.DEFAULT_WAIT);
9+
},
10+
postAMessage(message) {
11+
return this
12+
.waitForElementVisible('@postTextBox', Constants.DEFAULT_WAIT)
13+
.setValue('@postTextBox', message)
14+
.keys(this.Keys.ENTER)
15+
.waitForElementVisible('@postListContent', Constants.DEFAULT_WAIT);
16+
}
17+
};
18+
19+
module.exports = {
20+
url: `${Constants.TEST_BASE_URL}`,
21+
commands: [centerCommands],
22+
elements: {
23+
postTextBox: {
24+
selector: '//*[@id="post_textbox"]',
25+
locateStrategy: 'xpath'
26+
},
27+
fileAttachmentButton: {
28+
selector: '//*[@id="create_post"]/div/div[1]/div/span/span[1]/div',
29+
locateStrategy: 'xpath'
30+
},
31+
emojiPickerButton: {
32+
selector: '//*[@id="create_post"]/div/div[1]/div/span/span[2]/span',
33+
locateStrategy: 'xpath'
34+
},
35+
helpLink: {
36+
selector: '//*[@id="create_post"]/div/div[1]/div/div/div[3]/a',
37+
locateStrategy: 'xpath'
38+
},
39+
helpText: {
40+
selector: '//*[@id="create_post"]/div/div[1]/div/div/div[3]/div',
41+
locateStrategy: 'xpath'
42+
},
43+
postListContent: {
44+
selector: '//*[@id="post-list"]/div[2]/div/div',
45+
locateStrategy: 'xpath'
46+
}
47+
}
48+
};

tests/e2e/pages/loginPage.js

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
2+
// See License.txt for license information.
3+
4+
import {Constants} from '../utils';
5+
6+
const loginCommands = {
7+
navigateToPage() {
8+
return this.waitForElementVisible('@loginInput', Constants.DEFAULT_WAIT);
9+
},
10+
login(email, pass) {
11+
return this
12+
.waitForElementVisible('@loginInput', Constants.DEFAULT_WAIT)
13+
.setValue('@loginInput', email)
14+
.setValue('@passwordInput', pass)
15+
.waitForElementVisible('@signinButton', Constants.DEFAULT_WAIT)
16+
.click('@signinButton')
17+
.waitForElementVisible('@postTextBox', Constants.DEFAULT_WAIT);
18+
}
19+
};
20+
21+
module.exports = {
22+
url: `${Constants.TEST_BASE_URL}/login`,
23+
commands: [loginCommands],
24+
elements: {
25+
loginInput: {
26+
selector: 'input[name=loginId]'
27+
},
28+
passwordInput: {
29+
selector: 'input[name=password]'
30+
},
31+
signinButton: {
32+
selector: 'button[type=submit]'
33+
},
34+
postTextBox: {
35+
selector: '//*[@id="post_textbox"]',
36+
locateStrategy: 'xpath'
37+
}
38+
}
39+
};

0 commit comments

Comments
 (0)