aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/e2e
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2023-01-19 09:30:05 +0100
committerChocobozzz <chocobozzz@cpy.re>2023-01-19 13:53:40 +0100
commit5bdfa604f102a5e51b5152457cd1a16d79177e26 (patch)
tree878c04cbd3c452c7b45aa39e19abd38bfe691063 /client/e2e
parent9589907c89d29a6c0acd52c8cb789af9f93ce9af (diff)
downloadPeerTube-5bdfa604f102a5e51b5152457cd1a16d79177e26.tar.gz
PeerTube-5bdfa604f102a5e51b5152457cd1a16d79177e26.tar.zst
PeerTube-5bdfa604f102a5e51b5152457cd1a16d79177e26.zip
Add E2E client tests for signup approval
Diffstat (limited to 'client/e2e')
-rw-r--r--client/e2e/src/po/admin-config.po.ts27
-rw-r--r--client/e2e/src/po/admin-registration.po.ts35
-rw-r--r--client/e2e/src/po/login.po.ts36
-rw-r--r--client/e2e/src/po/signup.po.ts51
-rw-r--r--client/e2e/src/suites-local/signup.e2e-spec.ts403
-rw-r--r--client/e2e/src/utils/elements.ts17
-rw-r--r--client/e2e/src/utils/email.ts31
-rw-r--r--client/e2e/src/utils/hooks.ts24
-rw-r--r--client/e2e/src/utils/index.ts2
-rw-r--r--client/e2e/src/utils/mock-smtp.ts58
-rw-r--r--client/e2e/src/utils/server.ts4
-rw-r--r--client/e2e/wdio.local-test.conf.ts2
-rw-r--r--client/e2e/wdio.local.conf.ts2
13 files changed, 600 insertions, 92 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 @@
1import { getCheckbox, go } from '../utils' 1import { browserSleep, getCheckbox, go, isCheckboxSelected } from '../utils'
2 2
3export class AdminConfigPage { 3export 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 @@
1import { browserSleep, findParentElement, go } from '../utils'
2
3export 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 @@
1import { AdminConfigPage } from '../po/admin-config.po' 1import { AdminConfigPage } from '../po/admin-config.po'
2import { AdminRegistrationPage } from '../po/admin-registration.po'
2import { LoginPage } from '../po/login.po' 3import { LoginPage } from '../po/login.po'
3import { SignupPage } from '../po/signup.po' 4import { SignupPage } from '../po/signup.po'
4import { isMobileDevice, waitServerUp } from '../utils' 5import { browserSleep, getVerificationLink, go, findEmailTo, isMobileDevice, MockSMTPServer, waitServerUp } from '../utils'
6
7function 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
6describe('Signup', () => { 56describe('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
8function isCheckboxSelected (name: string) {
9 return $(`input[id=${name}]`).isSelected()
10}
11
8async function selectCustomSelect (id: string, valueLabel: string) { 12async 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
29async 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
25export { 38export {
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 @@
1function 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
18function 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
28export {
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 @@
1import { ChildProcessWithoutNullStreams } from 'child_process' 1import { ChildProcessWithoutNullStreams } from 'child_process'
2import { basename } from 'path' 2import { basename } from 'path'
3import { runCommand, runServer } from './server' 3import { runCommand, runServer } from './server'
4import { setValue } from '@wdio/shared-store-service'
4 5
5let appInstance: string 6let appInstance: number
6let app: ChildProcessWithoutNullStreams 7let app: ChildProcessWithoutNullStreams
7 8
9let emailPort: number
10
8async function beforeLocalSuite (suite: any) { 11async 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
20function beforeLocalSession (config: { baseUrl: string }, capabilities: { browserName: string }) { 23async 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
25async function onBrowserStackPrepare () { 35async 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 @@
1export * from './common' 1export * from './common'
2export * from './elements' 2export * from './elements'
3export * from './email'
3export * from './hooks' 4export * from './hooks'
5export * from './mock-smtp'
4export * from './server' 6export * from './server'
5export * from './urls' 7export * 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 @@
1import { ChildProcess } from 'child_process'
2import MailDev from '@peertube/maildev'
3
4class 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
56export {
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 @@
1import { exec, spawn } from 'child_process' 1import { exec, spawn } from 'child_process'
2import { join, resolve } from 'path' 2import { join, resolve } from 'path'
3 3
4function runServer (appInstance: string, config: any = {}) { 4function 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: {
diff --git a/client/e2e/wdio.local-test.conf.ts b/client/e2e/wdio.local-test.conf.ts
index ca0bb5bfe..bc15123a0 100644
--- a/client/e2e/wdio.local-test.conf.ts
+++ b/client/e2e/wdio.local-test.conf.ts
@@ -37,7 +37,7 @@ module.exports = {
37 // } 37 // }
38 ], 38 ],
39 39
40 services: [ 'chromedriver', 'geckodriver' ], 40 services: [ 'chromedriver', 'geckodriver', 'shared-store' ],
41 41
42 beforeSession: beforeLocalSession, 42 beforeSession: beforeLocalSession,
43 beforeSuite: beforeLocalSuite, 43 beforeSuite: beforeLocalSuite,
diff --git a/client/e2e/wdio.local.conf.ts b/client/e2e/wdio.local.conf.ts
index d02679e06..27c6e867b 100644
--- a/client/e2e/wdio.local.conf.ts
+++ b/client/e2e/wdio.local.conf.ts
@@ -33,7 +33,7 @@ module.exports = {
33 } 33 }
34 ], 34 ],
35 35
36 services: [ 'chromedriver', 'geckodriver' ], 36 services: [ 'chromedriver', 'geckodriver', 'shared-store' ],
37 37
38 beforeSession: beforeLocalSession, 38 beforeSession: beforeLocalSession,
39 beforeSuite: beforeLocalSuite, 39 beforeSuite: beforeLocalSuite,