diff options
author | Chocobozzz <me@florianbigard.com> | 2023-01-19 09:30:05 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2023-01-19 13:53:40 +0100 |
commit | 5bdfa604f102a5e51b5152457cd1a16d79177e26 (patch) | |
tree | 878c04cbd3c452c7b45aa39e19abd38bfe691063 /client/e2e/src | |
parent | 9589907c89d29a6c0acd52c8cb789af9f93ce9af (diff) | |
download | PeerTube-5bdfa604f102a5e51b5152457cd1a16d79177e26.tar.gz PeerTube-5bdfa604f102a5e51b5152457cd1a16d79177e26.tar.zst PeerTube-5bdfa604f102a5e51b5152457cd1a16d79177e26.zip |
Add E2E client tests for signup approval
Diffstat (limited to 'client/e2e/src')
-rw-r--r-- | client/e2e/src/po/admin-config.po.ts | 27 | ||||
-rw-r--r-- | client/e2e/src/po/admin-registration.po.ts | 35 | ||||
-rw-r--r-- | client/e2e/src/po/login.po.ts | 36 | ||||
-rw-r--r-- | client/e2e/src/po/signup.po.ts | 51 | ||||
-rw-r--r-- | client/e2e/src/suites-local/signup.e2e-spec.ts | 403 | ||||
-rw-r--r-- | client/e2e/src/utils/elements.ts | 17 | ||||
-rw-r--r-- | client/e2e/src/utils/email.ts | 31 | ||||
-rw-r--r-- | client/e2e/src/utils/hooks.ts | 24 | ||||
-rw-r--r-- | client/e2e/src/utils/index.ts | 2 | ||||
-rw-r--r-- | client/e2e/src/utils/mock-smtp.ts | 58 | ||||
-rw-r--r-- | client/e2e/src/utils/server.ts | 4 |
11 files changed, 598 insertions, 90 deletions
diff --git a/client/e2e/src/po/admin-config.po.ts b/client/e2e/src/po/admin-config.po.ts index 27957a71f..510037ddd 100644 --- a/client/e2e/src/po/admin-config.po.ts +++ b/client/e2e/src/po/admin-config.po.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { getCheckbox, go } from '../utils' | 1 | import { browserSleep, getCheckbox, go, isCheckboxSelected } from '../utils' |
2 | 2 | ||
3 | export class AdminConfigPage { | 3 | export class AdminConfigPage { |
4 | 4 | ||
@@ -8,7 +8,6 @@ export class AdminConfigPage { | |||
8 | 'basic-configuration': 'APPEARANCE', | 8 | 'basic-configuration': 'APPEARANCE', |
9 | 'instance-information': 'INSTANCE' | 9 | 'instance-information': 'INSTANCE' |
10 | } | 10 | } |
11 | |||
12 | await go('/admin/config/edit-custom#' + tab) | 11 | await go('/admin/config/edit-custom#' + tab) |
13 | 12 | ||
14 | await $('.inner-form-title=' + waitTitles[tab]).waitForDisplayed() | 13 | await $('.inner-form-title=' + waitTitles[tab]).waitForDisplayed() |
@@ -28,17 +27,39 @@ export class AdminConfigPage { | |||
28 | return $('#instanceCustomHomepageContent').setValue(newValue) | 27 | return $('#instanceCustomHomepageContent').setValue(newValue) |
29 | } | 28 | } |
30 | 29 | ||
31 | async toggleSignup () { | 30 | async toggleSignup (enabled: boolean) { |
31 | if (await isCheckboxSelected('signupEnabled') === enabled) return | ||
32 | |||
32 | const checkbox = await getCheckbox('signupEnabled') | 33 | const checkbox = await getCheckbox('signupEnabled') |
33 | 34 | ||
34 | await checkbox.waitForClickable() | 35 | await checkbox.waitForClickable() |
35 | await checkbox.click() | 36 | await checkbox.click() |
36 | } | 37 | } |
37 | 38 | ||
39 | async toggleSignupApproval (required: boolean) { | ||
40 | if (await isCheckboxSelected('signupRequiresApproval') === required) return | ||
41 | |||
42 | const checkbox = await getCheckbox('signupRequiresApproval') | ||
43 | |||
44 | await checkbox.waitForClickable() | ||
45 | await checkbox.click() | ||
46 | } | ||
47 | |||
48 | async toggleSignupEmailVerification (required: boolean) { | ||
49 | if (await isCheckboxSelected('signupRequiresEmailVerification') === required) return | ||
50 | |||
51 | const checkbox = await getCheckbox('signupRequiresEmailVerification') | ||
52 | |||
53 | await checkbox.waitForClickable() | ||
54 | await checkbox.click() | ||
55 | } | ||
56 | |||
38 | async save () { | 57 | async save () { |
39 | const button = $('input[type=submit]') | 58 | const button = $('input[type=submit]') |
40 | 59 | ||
41 | await button.waitForClickable() | 60 | await button.waitForClickable() |
42 | await button.click() | 61 | await button.click() |
62 | |||
63 | await browserSleep(1000) | ||
43 | } | 64 | } |
44 | } | 65 | } |
diff --git a/client/e2e/src/po/admin-registration.po.ts b/client/e2e/src/po/admin-registration.po.ts new file mode 100644 index 000000000..85234654d --- /dev/null +++ b/client/e2e/src/po/admin-registration.po.ts | |||
@@ -0,0 +1,35 @@ | |||
1 | import { browserSleep, findParentElement, go } from '../utils' | ||
2 | |||
3 | export class AdminRegistrationPage { | ||
4 | |||
5 | async navigateToRegistratonsList () { | ||
6 | await go('/admin/moderation/registrations/list') | ||
7 | |||
8 | await $('my-registration-list').waitForDisplayed() | ||
9 | } | ||
10 | |||
11 | async accept (username: string, moderationResponse: string) { | ||
12 | const usernameEl = await $('*=' + username) | ||
13 | await usernameEl.waitForDisplayed() | ||
14 | |||
15 | const tr = await findParentElement(usernameEl, async el => await el.getTagName() === 'tr') | ||
16 | |||
17 | await tr.$('.action-cell .dropdown-root').click() | ||
18 | |||
19 | const accept = await $('span*=Accept this registration') | ||
20 | await accept.waitForClickable() | ||
21 | await accept.click() | ||
22 | |||
23 | const moderationResponseTextarea = await $('#moderationResponse') | ||
24 | await moderationResponseTextarea.waitForDisplayed() | ||
25 | |||
26 | await moderationResponseTextarea.setValue(moderationResponse) | ||
27 | |||
28 | const submitButton = $('.modal-footer input[type=submit]') | ||
29 | await submitButton.waitForClickable() | ||
30 | await submitButton.click() | ||
31 | |||
32 | await browserSleep(1000) | ||
33 | } | ||
34 | |||
35 | } | ||
diff --git a/client/e2e/src/po/login.po.ts b/client/e2e/src/po/login.po.ts index bc1854dbc..f1d13a2b0 100644 --- a/client/e2e/src/po/login.po.ts +++ b/client/e2e/src/po/login.po.ts | |||
@@ -6,7 +6,14 @@ export class LoginPage { | |||
6 | 6 | ||
7 | } | 7 | } |
8 | 8 | ||
9 | async login (username: string, password: string, url = '/login') { | 9 | async login (options: { |
10 | username: string | ||
11 | password: string | ||
12 | displayName?: string | ||
13 | url?: string | ||
14 | }) { | ||
15 | const { username, password, url = '/login', displayName = username } = options | ||
16 | |||
10 | await go(url) | 17 | await go(url) |
11 | 18 | ||
12 | await browser.execute(`window.localStorage.setItem('no_account_setup_warning_modal', 'true')`) | 19 | await browser.execute(`window.localStorage.setItem('no_account_setup_warning_modal', 'true')`) |
@@ -27,27 +34,40 @@ export class LoginPage { | |||
27 | 34 | ||
28 | await menuToggle.click() | 35 | await menuToggle.click() |
29 | 36 | ||
30 | await this.ensureIsLoggedInAs(username) | 37 | await this.ensureIsLoggedInAs(displayName) |
31 | 38 | ||
32 | await menuToggle.click() | 39 | await menuToggle.click() |
33 | } else { | 40 | } else { |
34 | await this.ensureIsLoggedInAs(username) | 41 | await this.ensureIsLoggedInAs(displayName) |
35 | } | 42 | } |
36 | } | 43 | } |
37 | 44 | ||
45 | async getLoginError (username: string, password: string) { | ||
46 | await go('/login') | ||
47 | |||
48 | await $('input#username').setValue(username) | ||
49 | await $('input#password').setValue(password) | ||
50 | |||
51 | await browser.pause(1000) | ||
52 | |||
53 | await $('form input[type=submit]').click() | ||
54 | |||
55 | return $('.alert-danger').getText() | ||
56 | } | ||
57 | |||
38 | async loginAsRootUser () { | 58 | async loginAsRootUser () { |
39 | return this.login('root', 'test' + this.getSuffix()) | 59 | return this.login({ username: 'root', password: 'test' + this.getSuffix() }) |
40 | } | 60 | } |
41 | 61 | ||
42 | loginOnPeerTube2 () { | 62 | loginOnPeerTube2 () { |
43 | return this.login('e2e', process.env.PEERTUBE2_E2E_PASSWORD, 'https://peertube2.cpy.re/login') | 63 | return this.login({ username: 'e2e', password: process.env.PEERTUBE2_E2E_PASSWORD, url: 'https://peertube2.cpy.re/login' }) |
44 | } | 64 | } |
45 | 65 | ||
46 | async logout () { | 66 | async logout () { |
47 | const loggedInMore = $('.logged-in-more') | 67 | const loggedInDropdown = $('.logged-in-more .logged-in-info') |
48 | 68 | ||
49 | await loggedInMore.waitForClickable() | 69 | await loggedInDropdown.waitForClickable() |
50 | await loggedInMore.click() | 70 | await loggedInDropdown.click() |
51 | 71 | ||
52 | const logout = $('.dropdown-item*=Log out') | 72 | const logout = $('.dropdown-item*=Log out') |
53 | 73 | ||
diff --git a/client/e2e/src/po/signup.po.ts b/client/e2e/src/po/signup.po.ts index cc2ed7c5f..7917cdda7 100644 --- a/client/e2e/src/po/signup.po.ts +++ b/client/e2e/src/po/signup.po.ts | |||
@@ -27,42 +27,39 @@ export class SignupPage { | |||
27 | return terms.click() | 27 | return terms.click() |
28 | } | 28 | } |
29 | 29 | ||
30 | async getEndMessage () { | ||
31 | const alert = $('.pt-alert-primary') | ||
32 | await alert.waitForDisplayed() | ||
33 | |||
34 | return alert.getText() | ||
35 | } | ||
36 | |||
37 | async fillRegistrationReason (reason: string) { | ||
38 | await $('#registrationReason').setValue(reason) | ||
39 | } | ||
40 | |||
30 | async fillAccountStep (options: { | 41 | async fillAccountStep (options: { |
31 | displayName: string | ||
32 | username: string | 42 | username: string |
33 | email: string | 43 | password?: string |
34 | password: string | 44 | displayName?: string |
45 | email?: string | ||
35 | }) { | 46 | }) { |
36 | if (options.displayName) { | 47 | await $('#displayName').setValue(options.displayName || `${options.username} display name`) |
37 | await $('#displayName').setValue(options.displayName) | ||
38 | } | ||
39 | |||
40 | if (options.username) { | ||
41 | await $('#username').setValue(options.username) | ||
42 | } | ||
43 | 48 | ||
44 | if (options.email) { | 49 | await $('#username').setValue(options.username) |
45 | // Fix weird bug on firefox that "cannot scroll into view" when using just `setValue` | 50 | await $('#password').setValue(options.password || 'password') |
46 | await $('#email').scrollIntoView(false) | ||
47 | await $('#email').waitForClickable() | ||
48 | await $('#email').setValue(options.email) | ||
49 | } | ||
50 | 51 | ||
51 | if (options.password) { | 52 | // Fix weird bug on firefox that "cannot scroll into view" when using just `setValue` |
52 | await $('#password').setValue(options.password) | 53 | await $('#email').scrollIntoView(false) |
53 | } | 54 | await $('#email').waitForClickable() |
55 | await $('#email').setValue(options.email || `${options.username}@example.com`) | ||
54 | } | 56 | } |
55 | 57 | ||
56 | async fillChannelStep (options: { | 58 | async fillChannelStep (options: { |
57 | displayName: string | ||
58 | name: string | 59 | name: string |
60 | displayName?: string | ||
59 | }) { | 61 | }) { |
60 | if (options.displayName) { | 62 | await $('#displayName').setValue(options.displayName || `${options.name} channel display name`) |
61 | await $('#displayName').setValue(options.displayName) | 63 | await $('#name').setValue(options.name) |
62 | } | ||
63 | |||
64 | if (options.name) { | ||
65 | await $('#name').setValue(options.name) | ||
66 | } | ||
67 | } | 64 | } |
68 | } | 65 | } |
diff --git a/client/e2e/src/suites-local/signup.e2e-spec.ts b/client/e2e/src/suites-local/signup.e2e-spec.ts index 4eed3eefe..b6f7ad1a7 100644 --- a/client/e2e/src/suites-local/signup.e2e-spec.ts +++ b/client/e2e/src/suites-local/signup.e2e-spec.ts | |||
@@ -1,12 +1,89 @@ | |||
1 | import { AdminConfigPage } from '../po/admin-config.po' | 1 | import { AdminConfigPage } from '../po/admin-config.po' |
2 | import { AdminRegistrationPage } from '../po/admin-registration.po' | ||
2 | import { LoginPage } from '../po/login.po' | 3 | import { LoginPage } from '../po/login.po' |
3 | import { SignupPage } from '../po/signup.po' | 4 | import { SignupPage } from '../po/signup.po' |
4 | import { isMobileDevice, waitServerUp } from '../utils' | 5 | import { browserSleep, getVerificationLink, go, findEmailTo, isMobileDevice, MockSMTPServer, waitServerUp } from '../utils' |
6 | |||
7 | function checkEndMessage (options: { | ||
8 | message: string | ||
9 | requiresEmailVerification: boolean | ||
10 | requiresApproval: boolean | ||
11 | afterEmailVerification: boolean | ||
12 | }) { | ||
13 | const { message, requiresApproval, requiresEmailVerification, afterEmailVerification } = options | ||
14 | |||
15 | { | ||
16 | const created = 'account has been created' | ||
17 | const request = 'account request has been sent' | ||
18 | |||
19 | if (requiresApproval) { | ||
20 | expect(message).toContain(request) | ||
21 | expect(message).not.toContain(created) | ||
22 | } else { | ||
23 | expect(message).not.toContain(request) | ||
24 | expect(message).toContain(created) | ||
25 | } | ||
26 | } | ||
27 | |||
28 | { | ||
29 | const checkEmail = 'Check your emails' | ||
30 | |||
31 | if (requiresEmailVerification) { | ||
32 | expect(message).toContain(checkEmail) | ||
33 | } else { | ||
34 | expect(message).not.toContain(checkEmail) | ||
35 | |||
36 | const moderatorsApproval = 'moderator will check your registration request' | ||
37 | if (requiresApproval) { | ||
38 | expect(message).toContain(moderatorsApproval) | ||
39 | } else { | ||
40 | expect(message).not.toContain(moderatorsApproval) | ||
41 | } | ||
42 | } | ||
43 | } | ||
44 | |||
45 | { | ||
46 | const emailVerified = 'email has been verified' | ||
47 | |||
48 | if (afterEmailVerification) { | ||
49 | expect(message).toContain(emailVerified) | ||
50 | } else { | ||
51 | expect(message).not.toContain(emailVerified) | ||
52 | } | ||
53 | } | ||
54 | } | ||
5 | 55 | ||
6 | describe('Signup', () => { | 56 | describe('Signup', () => { |
7 | let loginPage: LoginPage | 57 | let loginPage: LoginPage |
8 | let adminConfigPage: AdminConfigPage | 58 | let adminConfigPage: AdminConfigPage |
9 | let signupPage: SignupPage | 59 | let signupPage: SignupPage |
60 | let adminRegistrationPage: AdminRegistrationPage | ||
61 | |||
62 | async function prepareSignup (options: { | ||
63 | enabled: boolean | ||
64 | requiresApproval?: boolean | ||
65 | requiresEmailVerification?: boolean | ||
66 | }) { | ||
67 | await loginPage.loginAsRootUser() | ||
68 | |||
69 | await adminConfigPage.navigateTo('basic-configuration') | ||
70 | await adminConfigPage.toggleSignup(options.enabled) | ||
71 | |||
72 | if (options.enabled) { | ||
73 | if (options.requiresApproval !== undefined) { | ||
74 | await adminConfigPage.toggleSignupApproval(options.requiresApproval) | ||
75 | } | ||
76 | |||
77 | if (options.requiresEmailVerification !== undefined) { | ||
78 | await adminConfigPage.toggleSignupEmailVerification(options.requiresEmailVerification) | ||
79 | } | ||
80 | } | ||
81 | |||
82 | await adminConfigPage.save() | ||
83 | |||
84 | await loginPage.logout() | ||
85 | await browser.refresh() | ||
86 | } | ||
10 | 87 | ||
11 | before(async () => { | 88 | before(async () => { |
12 | await waitServerUp() | 89 | await waitServerUp() |
@@ -16,72 +93,310 @@ describe('Signup', () => { | |||
16 | loginPage = new LoginPage(isMobileDevice()) | 93 | loginPage = new LoginPage(isMobileDevice()) |
17 | adminConfigPage = new AdminConfigPage() | 94 | adminConfigPage = new AdminConfigPage() |
18 | signupPage = new SignupPage() | 95 | signupPage = new SignupPage() |
96 | adminRegistrationPage = new AdminRegistrationPage() | ||
19 | 97 | ||
20 | await browser.maximizeWindow() | 98 | await browser.maximizeWindow() |
21 | }) | 99 | }) |
22 | 100 | ||
23 | it('Should disable signup', async () => { | 101 | describe('Signup disabled', function () { |
24 | await loginPage.loginAsRootUser() | 102 | it('Should disable signup', async () => { |
103 | await prepareSignup({ enabled: false }) | ||
25 | 104 | ||
26 | await adminConfigPage.navigateTo('basic-configuration') | 105 | await expect(signupPage.getRegisterMenuButton()).not.toBeDisplayed() |
27 | await adminConfigPage.toggleSignup() | 106 | }) |
107 | }) | ||
28 | 108 | ||
29 | await adminConfigPage.save() | 109 | describe('Email verification disabled', function () { |
30 | 110 | ||
31 | await loginPage.logout() | 111 | describe('Direct registration', function () { |
32 | await browser.refresh() | ||
33 | 112 | ||
34 | expect(signupPage.getRegisterMenuButton()).not.toBeDisplayed() | 113 | it('Should enable signup without approval', async () => { |
35 | }) | 114 | await prepareSignup({ enabled: true, requiresApproval: false, requiresEmailVerification: false }) |
36 | 115 | ||
37 | it('Should enable signup', async () => { | 116 | await signupPage.getRegisterMenuButton().waitForDisplayed() |
38 | await loginPage.loginAsRootUser() | 117 | }) |
39 | 118 | ||
40 | await adminConfigPage.navigateTo('basic-configuration') | 119 | it('Should go on signup page', async function () { |
41 | await adminConfigPage.toggleSignup() | 120 | await signupPage.clickOnRegisterInMenu() |
121 | }) | ||
42 | 122 | ||
43 | await adminConfigPage.save() | 123 | it('Should validate the first step (about page)', async function () { |
124 | await signupPage.validateStep() | ||
125 | }) | ||
44 | 126 | ||
45 | await loginPage.logout() | 127 | it('Should validate the second step (terms)', async function () { |
46 | await browser.refresh() | 128 | await signupPage.checkTerms() |
129 | await signupPage.validateStep() | ||
130 | }) | ||
47 | 131 | ||
48 | expect(signupPage.getRegisterMenuButton()).toBeDisplayed() | 132 | it('Should validate the third step (account)', async function () { |
49 | }) | 133 | await signupPage.fillAccountStep({ username: 'user_1', displayName: 'user_1_dn' }) |
50 | 134 | ||
51 | it('Should go on signup page', async function () { | 135 | await signupPage.validateStep() |
52 | await signupPage.clickOnRegisterInMenu() | 136 | }) |
53 | }) | ||
54 | 137 | ||
55 | it('Should validate the first step (about page)', async function () { | 138 | it('Should validate the third step (channel)', async function () { |
56 | await signupPage.validateStep() | 139 | await signupPage.fillChannelStep({ name: 'user_1_channel' }) |
57 | }) | ||
58 | 140 | ||
59 | it('Should validate the second step (terms)', async function () { | 141 | await signupPage.validateStep() |
60 | await signupPage.checkTerms() | 142 | }) |
61 | await signupPage.validateStep() | 143 | |
62 | }) | 144 | it('Should be logged in', async function () { |
145 | await loginPage.ensureIsLoggedInAs('user_1_dn') | ||
146 | }) | ||
147 | |||
148 | it('Should have a valid end message', async function () { | ||
149 | const message = await signupPage.getEndMessage() | ||
150 | |||
151 | checkEndMessage({ | ||
152 | message, | ||
153 | requiresEmailVerification: false, | ||
154 | requiresApproval: false, | ||
155 | afterEmailVerification: false | ||
156 | }) | ||
63 | 157 | ||
64 | it('Should validate the third step (account)', async function () { | 158 | await browser.saveScreenshot('./screenshots/direct-without-email.png') |
65 | await signupPage.fillAccountStep({ | 159 | |
66 | displayName: 'user 1', | 160 | await loginPage.logout() |
67 | username: 'user_1', | 161 | }) |
68 | email: 'user_1@example.com', | ||
69 | password: 'my_super_password' | ||
70 | }) | 162 | }) |
71 | 163 | ||
72 | await signupPage.validateStep() | 164 | describe('Registration with approval', function () { |
165 | |||
166 | it('Should enable signup with approval', async () => { | ||
167 | await prepareSignup({ enabled: true, requiresApproval: true, requiresEmailVerification: false }) | ||
168 | |||
169 | await signupPage.getRegisterMenuButton().waitForDisplayed() | ||
170 | }) | ||
171 | |||
172 | it('Should go on signup page', async function () { | ||
173 | await signupPage.clickOnRegisterInMenu() | ||
174 | }) | ||
175 | |||
176 | it('Should validate the first step (about page)', async function () { | ||
177 | await signupPage.validateStep() | ||
178 | }) | ||
179 | |||
180 | it('Should validate the second step (terms)', async function () { | ||
181 | await signupPage.checkTerms() | ||
182 | await signupPage.fillRegistrationReason('my super reason') | ||
183 | await signupPage.validateStep() | ||
184 | }) | ||
185 | |||
186 | it('Should validate the third step (account)', async function () { | ||
187 | await signupPage.fillAccountStep({ username: 'user_2', displayName: 'user_2 display name', password: 'password' }) | ||
188 | await signupPage.validateStep() | ||
189 | }) | ||
190 | |||
191 | it('Should validate the third step (channel)', async function () { | ||
192 | await signupPage.fillChannelStep({ name: 'user_2_channel' }) | ||
193 | await signupPage.validateStep() | ||
194 | }) | ||
195 | |||
196 | it('Should have a valid end message', async function () { | ||
197 | const message = await signupPage.getEndMessage() | ||
198 | |||
199 | checkEndMessage({ | ||
200 | message, | ||
201 | requiresEmailVerification: false, | ||
202 | requiresApproval: true, | ||
203 | afterEmailVerification: false | ||
204 | }) | ||
205 | |||
206 | await browser.saveScreenshot('./screenshots/request-without-email.png') | ||
207 | }) | ||
208 | |||
209 | it('Should display a message when trying to login with this account', async function () { | ||
210 | const error = await loginPage.getLoginError('user_2', 'password') | ||
211 | |||
212 | expect(error).toContain('awaiting approval') | ||
213 | }) | ||
214 | |||
215 | it('Should accept the registration', async function () { | ||
216 | await loginPage.loginAsRootUser() | ||
217 | |||
218 | await adminRegistrationPage.navigateToRegistratonsList() | ||
219 | await adminRegistrationPage.accept('user_2', 'moderation response') | ||
220 | |||
221 | await loginPage.logout() | ||
222 | }) | ||
223 | |||
224 | it('Should be able to login with this new account', async function () { | ||
225 | await loginPage.login({ username: 'user_2', password: 'password', displayName: 'user_2 display name' }) | ||
226 | |||
227 | await loginPage.logout() | ||
228 | }) | ||
229 | }) | ||
73 | }) | 230 | }) |
74 | 231 | ||
75 | it('Should validate the third step (channel)', async function () { | 232 | describe('Email verification enabled', function () { |
76 | await signupPage.fillChannelStep({ | 233 | const emails: any[] = [] |
77 | displayName: 'user 1 channel', | 234 | let emailPort: number |
78 | name: 'user_1_channel' | 235 | |
236 | before(async () => { | ||
237 | // FIXME: typings are wrong, get returns a promise | ||
238 | emailPort = await browser.sharedStore.get('emailPort') as unknown as number | ||
239 | |||
240 | MockSMTPServer.Instance.collectEmails(emailPort, emails) | ||
79 | }) | 241 | }) |
80 | 242 | ||
81 | await signupPage.validateStep() | 243 | describe('Direct registration', function () { |
82 | }) | 244 | |
245 | it('Should enable signup without approval', async () => { | ||
246 | await prepareSignup({ enabled: true, requiresApproval: false, requiresEmailVerification: true }) | ||
247 | |||
248 | await signupPage.getRegisterMenuButton().waitForDisplayed() | ||
249 | }) | ||
250 | |||
251 | it('Should go on signup page', async function () { | ||
252 | await signupPage.clickOnRegisterInMenu() | ||
253 | }) | ||
254 | |||
255 | it('Should validate the first step (about page)', async function () { | ||
256 | await signupPage.validateStep() | ||
257 | }) | ||
258 | |||
259 | it('Should validate the second step (terms)', async function () { | ||
260 | await signupPage.checkTerms() | ||
261 | await signupPage.validateStep() | ||
262 | }) | ||
263 | |||
264 | it('Should validate the third step (account)', async function () { | ||
265 | await signupPage.fillAccountStep({ username: 'user_3', displayName: 'user_3 display name', email: 'user_3@example.com' }) | ||
266 | |||
267 | await signupPage.validateStep() | ||
268 | }) | ||
269 | |||
270 | it('Should validate the third step (channel)', async function () { | ||
271 | await signupPage.fillChannelStep({ name: 'user_3_channel' }) | ||
272 | |||
273 | await signupPage.validateStep() | ||
274 | }) | ||
275 | |||
276 | it('Should have a valid end message', async function () { | ||
277 | const message = await signupPage.getEndMessage() | ||
278 | |||
279 | checkEndMessage({ | ||
280 | message, | ||
281 | requiresEmailVerification: true, | ||
282 | requiresApproval: false, | ||
283 | afterEmailVerification: false | ||
284 | }) | ||
285 | |||
286 | await browser.saveScreenshot('./screenshots/direct-with-email.png') | ||
287 | }) | ||
288 | |||
289 | it('Should validate the email', async function () { | ||
290 | let email: { text: string } | ||
291 | |||
292 | while (!(email = findEmailTo(emails, 'user_3@example.com'))) { | ||
293 | await browserSleep(100) | ||
294 | } | ||
295 | |||
296 | await go(getVerificationLink(email)) | ||
297 | |||
298 | const message = await signupPage.getEndMessage() | ||
299 | |||
300 | checkEndMessage({ | ||
301 | message, | ||
302 | requiresEmailVerification: false, | ||
303 | requiresApproval: false, | ||
304 | afterEmailVerification: true | ||
305 | }) | ||
83 | 306 | ||
84 | it('Should be logged in', async function () { | 307 | await browser.saveScreenshot('./screenshots/direct-after-email.png') |
85 | await loginPage.ensureIsLoggedInAs('user 1') | 308 | }) |
309 | }) | ||
310 | |||
311 | describe('Registration with approval', function () { | ||
312 | |||
313 | it('Should enable signup without approval', async () => { | ||
314 | await prepareSignup({ enabled: true, requiresApproval: true, requiresEmailVerification: true }) | ||
315 | |||
316 | await signupPage.getRegisterMenuButton().waitForDisplayed() | ||
317 | }) | ||
318 | |||
319 | it('Should go on signup page', async function () { | ||
320 | await signupPage.clickOnRegisterInMenu() | ||
321 | }) | ||
322 | |||
323 | it('Should validate the first step (about page)', async function () { | ||
324 | await signupPage.validateStep() | ||
325 | }) | ||
326 | |||
327 | it('Should validate the second step (terms)', async function () { | ||
328 | await signupPage.checkTerms() | ||
329 | await signupPage.fillRegistrationReason('my super reason 2') | ||
330 | await signupPage.validateStep() | ||
331 | }) | ||
332 | |||
333 | it('Should validate the third step (account)', async function () { | ||
334 | await signupPage.fillAccountStep({ | ||
335 | username: 'user_4', | ||
336 | displayName: 'user_4 display name', | ||
337 | email: 'user_4@example.com', | ||
338 | password: 'password' | ||
339 | }) | ||
340 | await signupPage.validateStep() | ||
341 | }) | ||
342 | |||
343 | it('Should validate the third step (channel)', async function () { | ||
344 | await signupPage.fillChannelStep({ name: 'user_4_channel' }) | ||
345 | await signupPage.validateStep() | ||
346 | }) | ||
347 | |||
348 | it('Should have a valid end message', async function () { | ||
349 | const message = await signupPage.getEndMessage() | ||
350 | |||
351 | checkEndMessage({ | ||
352 | message, | ||
353 | requiresEmailVerification: true, | ||
354 | requiresApproval: true, | ||
355 | afterEmailVerification: false | ||
356 | }) | ||
357 | |||
358 | await browser.saveScreenshot('./screenshots/request-with-email.png') | ||
359 | }) | ||
360 | |||
361 | it('Should display a message when trying to login with this account', async function () { | ||
362 | const error = await loginPage.getLoginError('user_4', 'password') | ||
363 | |||
364 | expect(error).toContain('awaiting approval') | ||
365 | }) | ||
366 | |||
367 | it('Should accept the registration', async function () { | ||
368 | await loginPage.loginAsRootUser() | ||
369 | |||
370 | await adminRegistrationPage.navigateToRegistratonsList() | ||
371 | await adminRegistrationPage.accept('user_4', 'moderation response 2') | ||
372 | |||
373 | await loginPage.logout() | ||
374 | }) | ||
375 | |||
376 | it('Should validate the email', async function () { | ||
377 | let email: { text: string } | ||
378 | |||
379 | while (!(email = findEmailTo(emails, 'user_4@example.com'))) { | ||
380 | await browserSleep(100) | ||
381 | } | ||
382 | |||
383 | await go(getVerificationLink(email)) | ||
384 | |||
385 | const message = await signupPage.getEndMessage() | ||
386 | |||
387 | checkEndMessage({ | ||
388 | message, | ||
389 | requiresEmailVerification: false, | ||
390 | requiresApproval: true, | ||
391 | afterEmailVerification: true | ||
392 | }) | ||
393 | |||
394 | await browser.saveScreenshot('./screenshots/request-after-email.png') | ||
395 | }) | ||
396 | }) | ||
397 | |||
398 | before(() => { | ||
399 | MockSMTPServer.Instance.kill() | ||
400 | }) | ||
86 | }) | 401 | }) |
87 | }) | 402 | }) |
diff --git a/client/e2e/src/utils/elements.ts b/client/e2e/src/utils/elements.ts index b0ddd5a65..d9435e520 100644 --- a/client/e2e/src/utils/elements.ts +++ b/client/e2e/src/utils/elements.ts | |||
@@ -5,6 +5,10 @@ async function getCheckbox (name: string) { | |||
5 | return input.parentElement() | 5 | return input.parentElement() |
6 | } | 6 | } |
7 | 7 | ||
8 | function isCheckboxSelected (name: string) { | ||
9 | return $(`input[id=${name}]`).isSelected() | ||
10 | } | ||
11 | |||
8 | async function selectCustomSelect (id: string, valueLabel: string) { | 12 | async function selectCustomSelect (id: string, valueLabel: string) { |
9 | const wrapper = $(`[formcontrolname=${id}] .ng-arrow-wrapper`) | 13 | const wrapper = $(`[formcontrolname=${id}] .ng-arrow-wrapper`) |
10 | 14 | ||
@@ -22,7 +26,18 @@ async function selectCustomSelect (id: string, valueLabel: string) { | |||
22 | return option.click() | 26 | return option.click() |
23 | } | 27 | } |
24 | 28 | ||
29 | async function findParentElement ( | ||
30 | el: WebdriverIO.Element, | ||
31 | finder: (el: WebdriverIO.Element) => Promise<boolean> | ||
32 | ) { | ||
33 | if (await finder(el) === true) return el | ||
34 | |||
35 | return findParentElement(await el.parentElement(), finder) | ||
36 | } | ||
37 | |||
25 | export { | 38 | export { |
26 | getCheckbox, | 39 | getCheckbox, |
27 | selectCustomSelect | 40 | isCheckboxSelected, |
41 | selectCustomSelect, | ||
42 | findParentElement | ||
28 | } | 43 | } |
diff --git a/client/e2e/src/utils/email.ts b/client/e2e/src/utils/email.ts new file mode 100644 index 000000000..2ad120333 --- /dev/null +++ b/client/e2e/src/utils/email.ts | |||
@@ -0,0 +1,31 @@ | |||
1 | function getVerificationLink (email: { text: string }) { | ||
2 | const { text } = email | ||
3 | |||
4 | const regexp = /\[(?<link>http:\/\/[^\]]+)\]/g | ||
5 | const matched = text.matchAll(regexp) | ||
6 | |||
7 | if (!matched) throw new Error('Could not find verification link in email') | ||
8 | |||
9 | for (const match of matched) { | ||
10 | const link = match.groups.link | ||
11 | |||
12 | if (link.includes('/verify-account/')) return link | ||
13 | } | ||
14 | |||
15 | throw new Error('Could not find /verify-account/ link') | ||
16 | } | ||
17 | |||
18 | function findEmailTo (emails: { text: string, to: { address: string }[] }[], to: string) { | ||
19 | for (const email of emails) { | ||
20 | for (const { address } of email.to) { | ||
21 | if (address === to) return email | ||
22 | } | ||
23 | } | ||
24 | |||
25 | return undefined | ||
26 | } | ||
27 | |||
28 | export { | ||
29 | getVerificationLink, | ||
30 | findEmailTo | ||
31 | } | ||
diff --git a/client/e2e/src/utils/hooks.ts b/client/e2e/src/utils/hooks.ts index 889cf1d86..7fe247681 100644 --- a/client/e2e/src/utils/hooks.ts +++ b/client/e2e/src/utils/hooks.ts | |||
@@ -1,10 +1,13 @@ | |||
1 | import { ChildProcessWithoutNullStreams } from 'child_process' | 1 | import { ChildProcessWithoutNullStreams } from 'child_process' |
2 | import { basename } from 'path' | 2 | import { basename } from 'path' |
3 | import { runCommand, runServer } from './server' | 3 | import { runCommand, runServer } from './server' |
4 | import { setValue } from '@wdio/shared-store-service' | ||
4 | 5 | ||
5 | let appInstance: string | 6 | let appInstance: number |
6 | let app: ChildProcessWithoutNullStreams | 7 | let app: ChildProcessWithoutNullStreams |
7 | 8 | ||
9 | let emailPort: number | ||
10 | |||
8 | async function beforeLocalSuite (suite: any) { | 11 | async function beforeLocalSuite (suite: any) { |
9 | const config = buildConfig(suite.file) | 12 | const config = buildConfig(suite.file) |
10 | 13 | ||
@@ -17,13 +20,20 @@ function afterLocalSuite () { | |||
17 | app = undefined | 20 | app = undefined |
18 | } | 21 | } |
19 | 22 | ||
20 | function beforeLocalSession (config: { baseUrl: string }, capabilities: { browserName: string }) { | 23 | async function beforeLocalSession (config: { baseUrl: string }, capabilities: { browserName: string }) { |
21 | appInstance = capabilities['browserName'] === 'chrome' ? '1' : '2' | 24 | appInstance = capabilities['browserName'] === 'chrome' |
25 | ? 1 | ||
26 | : 2 | ||
27 | |||
28 | emailPort = 1025 + appInstance | ||
29 | |||
22 | config.baseUrl = 'http://localhost:900' + appInstance | 30 | config.baseUrl = 'http://localhost:900' + appInstance |
31 | |||
32 | await setValue('emailPort', emailPort) | ||
23 | } | 33 | } |
24 | 34 | ||
25 | async function onBrowserStackPrepare () { | 35 | async function onBrowserStackPrepare () { |
26 | const appInstance = '1' | 36 | const appInstance = 1 |
27 | 37 | ||
28 | await runCommand('npm run clean:server:test -- ' + appInstance) | 38 | await runCommand('npm run clean:server:test -- ' + appInstance) |
29 | app = runServer(appInstance) | 39 | app = runServer(appInstance) |
@@ -71,7 +81,11 @@ function buildConfig (suiteFile: string = undefined) { | |||
71 | if (filename === 'signup.e2e-spec.ts') { | 81 | if (filename === 'signup.e2e-spec.ts') { |
72 | return { | 82 | return { |
73 | signup: { | 83 | signup: { |
74 | enabled: true | 84 | limit: -1 |
85 | }, | ||
86 | smtp: { | ||
87 | hostname: '127.0.0.1', | ||
88 | port: emailPort | ||
75 | } | 89 | } |
76 | } | 90 | } |
77 | } | 91 | } |
diff --git a/client/e2e/src/utils/index.ts b/client/e2e/src/utils/index.ts index 354352ee2..420fd239e 100644 --- a/client/e2e/src/utils/index.ts +++ b/client/e2e/src/utils/index.ts | |||
@@ -1,5 +1,7 @@ | |||
1 | export * from './common' | 1 | export * from './common' |
2 | export * from './elements' | 2 | export * from './elements' |
3 | export * from './email' | ||
3 | export * from './hooks' | 4 | export * from './hooks' |
5 | export * from './mock-smtp' | ||
4 | export * from './server' | 6 | export * from './server' |
5 | export * from './urls' | 7 | export * from './urls' |
diff --git a/client/e2e/src/utils/mock-smtp.ts b/client/e2e/src/utils/mock-smtp.ts new file mode 100644 index 000000000..614477d7d --- /dev/null +++ b/client/e2e/src/utils/mock-smtp.ts | |||
@@ -0,0 +1,58 @@ | |||
1 | import { ChildProcess } from 'child_process' | ||
2 | import MailDev from '@peertube/maildev' | ||
3 | |||
4 | class MockSMTPServer { | ||
5 | |||
6 | private static instance: MockSMTPServer | ||
7 | private started = false | ||
8 | private emailChildProcess: ChildProcess | ||
9 | private emails: object[] | ||
10 | |||
11 | collectEmails (port: number, emailsCollection: object[]) { | ||
12 | return new Promise<number>((res, rej) => { | ||
13 | this.emails = emailsCollection | ||
14 | |||
15 | if (this.started) { | ||
16 | return res(undefined) | ||
17 | } | ||
18 | |||
19 | const maildev = new MailDev({ | ||
20 | ip: '127.0.0.1', | ||
21 | smtp: port, | ||
22 | disableWeb: true, | ||
23 | silent: true | ||
24 | }) | ||
25 | |||
26 | maildev.on('new', email => { | ||
27 | this.emails.push(email) | ||
28 | }) | ||
29 | |||
30 | maildev.listen(err => { | ||
31 | if (err) return rej(err) | ||
32 | |||
33 | this.started = true | ||
34 | |||
35 | return res(port) | ||
36 | }) | ||
37 | }) | ||
38 | } | ||
39 | |||
40 | kill () { | ||
41 | if (!this.emailChildProcess) return | ||
42 | |||
43 | process.kill(this.emailChildProcess.pid) | ||
44 | |||
45 | this.emailChildProcess = null | ||
46 | MockSMTPServer.instance = null | ||
47 | } | ||
48 | |||
49 | static get Instance () { | ||
50 | return this.instance || (this.instance = new this()) | ||
51 | } | ||
52 | } | ||
53 | |||
54 | // --------------------------------------------------------------------------- | ||
55 | |||
56 | export { | ||
57 | MockSMTPServer | ||
58 | } | ||
diff --git a/client/e2e/src/utils/server.ts b/client/e2e/src/utils/server.ts index 140054794..227f4aea6 100644 --- a/client/e2e/src/utils/server.ts +++ b/client/e2e/src/utils/server.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { exec, spawn } from 'child_process' | 1 | import { exec, spawn } from 'child_process' |
2 | import { join, resolve } from 'path' | 2 | import { join, resolve } from 'path' |
3 | 3 | ||
4 | function runServer (appInstance: string, config: any = {}) { | 4 | function runServer (appInstance: number, config: any = {}) { |
5 | const env = Object.create(process.env) | 5 | const env = Object.create(process.env) |
6 | env['NODE_ENV'] = 'test' | 6 | env['NODE_ENV'] = 'test' |
7 | env['NODE_APP_INSTANCE'] = appInstance | 7 | env['NODE_APP_INSTANCE'] = appInstance + '' |
8 | 8 | ||
9 | env['NODE_CONFIG'] = JSON.stringify({ | 9 | env['NODE_CONFIG'] = JSON.stringify({ |
10 | rates_limit: { | 10 | rates_limit: { |