aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/angular.json1
-rw-r--r--client/e2e/src/po/admin-config.po.ts29
-rw-r--r--client/e2e/src/po/login.po.ts20
-rw-r--r--client/e2e/src/po/my-account.ts18
-rw-r--r--client/e2e/src/po/player.po.ts5
-rw-r--r--client/e2e/src/po/video-list.po.ts128
-rw-r--r--client/e2e/src/po/video-search.po.ts11
-rw-r--r--client/e2e/src/po/video-upload.po.ts5
-rw-r--r--client/e2e/src/po/video-watch.po.ts67
-rw-r--r--client/e2e/src/suites-all/videos.e2e-spec.ts (renamed from client/e2e/src/videos.e2e-spec.ts)37
-rw-r--r--client/e2e/src/suites-local/videos-list.e2e-spec.ts195
-rw-r--r--client/e2e/src/types/common.ts1
-rw-r--r--client/e2e/src/utils/common.ts (renamed from client/e2e/src/utils.ts)10
-rw-r--r--client/e2e/src/utils/elements.ts7
-rw-r--r--client/e2e/src/utils/index.ts3
-rw-r--r--client/e2e/src/utils/urls.ts (renamed from client/e2e/src/urls.ts)0
-rw-r--r--client/e2e/tsconfig.json2
-rw-r--r--client/e2e/wdio.browserstack.conf.ts21
-rw-r--r--client/e2e/wdio.local-test.conf.ts28
-rw-r--r--client/e2e/wdio.local.conf.ts22
-rw-r--r--client/e2e/wdio.main.conf.ts13
-rw-r--r--client/src/app/core/users/user.service.ts6
-rw-r--r--client/src/app/shared/shared-video-miniature/video-filters-header.component.ts6
-rw-r--r--client/src/app/shared/shared-video-miniature/video-filters.model.ts2
-rw-r--r--client/src/app/shared/shared-video-miniature/videos-list.component.html2
-rwxr-xr-xscripts/e2e/browserstack.sh2
-rwxr-xr-xscripts/e2e/local.sh10
-rw-r--r--support/doc/development/tests.md15
28 files changed, 550 insertions, 116 deletions
diff --git a/client/angular.json b/client/angular.json
index 0ec6f68bb..69de2e784 100644
--- a/client/angular.json
+++ b/client/angular.json
@@ -281,6 +281,7 @@
281 "builder": "@angular-eslint/builder:lint", 281 "builder": "@angular-eslint/builder:lint",
282 "options": { 282 "options": {
283 "lintFilePatterns": [ 283 "lintFilePatterns": [
284 "e2e/**/*.ts",
284 "src/**/*.ts", 285 "src/**/*.ts",
285 "src/**/*.html" 286 "src/**/*.html"
286 ] 287 ]
diff --git a/client/e2e/src/po/admin-config.po.ts b/client/e2e/src/po/admin-config.po.ts
new file mode 100644
index 000000000..a15184781
--- /dev/null
+++ b/client/e2e/src/po/admin-config.po.ts
@@ -0,0 +1,29 @@
1import { browserSleep, go } from '../utils'
2
3export class AdminConfigPage {
4
5 async navigateTo (tab: 'instance-homepage' | 'basic-configuration' | 'instance-information') {
6 const waitTitles = {
7 'instance-homepage': 'INSTANCE HOMEPAGE',
8 'basic-configuration': 'APPEARANCE',
9 'instance-information': 'INSTANCE'
10 }
11
12 await go('/admin/config/edit-custom#' + tab)
13
14 await $('.inner-form-title=' + waitTitles[tab]).waitForDisplayed()
15 }
16
17 updateNSFWSetting (newValue: 'do_not_list' | 'blur' | 'display') {
18 return $('#instanceDefaultNSFWPolicy').selectByAttribute('value', newValue)
19 }
20
21 updateHomepage (newValue: string) {
22 return $('#instanceCustomHomepageContent').setValue(newValue)
23 }
24
25 async save () {
26 await $('input[type=submit]').click()
27 await browserSleep(200)
28 }
29}
diff --git a/client/e2e/src/po/login.po.ts b/client/e2e/src/po/login.po.ts
index 8e3030e43..486b9a6d8 100644
--- a/client/e2e/src/po/login.po.ts
+++ b/client/e2e/src/po/login.po.ts
@@ -1,6 +1,7 @@
1import { go } from '../utils' 1import { go } from '../utils'
2 2
3export class LoginPage { 3export class LoginPage {
4
4 async loginAsRootUser () { 5 async loginAsRootUser () {
5 await go('/login') 6 await go('/login')
6 7
@@ -8,7 +9,7 @@ export class LoginPage {
8 await browser.execute(`window.localStorage.setItem('no_welcome_modal', 'true')`) 9 await browser.execute(`window.localStorage.setItem('no_welcome_modal', 'true')`)
9 10
10 await $('input#username').setValue('root') 11 await $('input#username').setValue('root')
11 await $('input#password').setValue('test1') 12 await $('input#password').setValue('test' + this.getSuffix())
12 13
13 await browser.pause(1000) 14 await browser.pause(1000)
14 15
@@ -19,7 +20,24 @@ export class LoginPage {
19 await expect(this.getLoggedInInfoElem()).toHaveText('root') 20 await expect(this.getLoggedInInfoElem()).toHaveText('root')
20 } 21 }
21 22
23 async logout () {
24 await $('.logged-in-more').click()
25
26 const logout = () => $('.dropdown-item*=Log out')
27
28 await logout().waitForDisplayed()
29 await logout().click()
30
31 await $('.login-buttons-block').waitForDisplayed()
32 }
33
22 private getLoggedInInfoElem () { 34 private getLoggedInInfoElem () {
23 return $('.logged-in-display-name') 35 return $('.logged-in-display-name')
24 } 36 }
37
38 private getSuffix () {
39 return browser.config.baseUrl
40 ? browser.config.baseUrl.slice(-1)
41 : '1'
42 }
25} 43}
diff --git a/client/e2e/src/po/my-account.ts b/client/e2e/src/po/my-account.ts
index 85dc02805..8b5e79b5e 100644
--- a/client/e2e/src/po/my-account.ts
+++ b/client/e2e/src/po/my-account.ts
@@ -14,6 +14,24 @@ export class MyAccountPage {
14 return $('a[href="/my-library/history/videos"]').click() 14 return $('a[href="/my-library/history/videos"]').click()
15 } 15 }
16 16
17 // Settings
18
19 navigateToMySettings () {
20 return $('a[href="/my-account"]').click()
21 }
22
23 async updateNSFW (newValue: 'do_not_list' | 'blur' | 'display') {
24 const nsfw = $('#nsfwPolicy')
25
26 await nsfw.waitForDisplayed()
27 await nsfw.scrollIntoView(false) // Avoid issues with fixed header on firefox
28 await nsfw.selectByAttribute('value', newValue)
29
30 const submit = $('my-user-video-settings input[type=submit]')
31 await submit.scrollIntoView(false)
32 await submit.click()
33 }
34
17 // My account Videos 35 // My account Videos
18 36
19 async removeVideo (name: string) { 37 async removeVideo (name: string) {
diff --git a/client/e2e/src/po/player.po.ts b/client/e2e/src/po/player.po.ts
index 372e8ab20..fca3bcdba 100644
--- a/client/e2e/src/po/player.po.ts
+++ b/client/e2e/src/po/player.po.ts
@@ -15,6 +15,9 @@ export class PlayerPage {
15 15
16 waitUntilPlaylistInfo (text: string, maxTime: number) { 16 waitUntilPlaylistInfo (text: string, maxTime: number) {
17 return browser.waitUntil(async () => { 17 return browser.waitUntil(async () => {
18 // Without this we have issues on iphone
19 await $('.video-js').click()
20
18 return (await $('.video-js .vjs-playlist-info').getText()).includes(text) 21 return (await $('.video-js .vjs-playlist-info').getText()).includes(text)
19 }, { timeout: maxTime }) 22 }, { timeout: maxTime })
20 } 23 }
@@ -42,7 +45,7 @@ export class PlayerPage {
42 await browserSleep(2000) 45 await browserSleep(2000)
43 46
44 await browser.waitUntil(async () => { 47 await browser.waitUntil(async () => {
45 return (await this.getWatchVideoPlayerCurrentTime()) >= 2 48 return (await this.getWatchVideoPlayerCurrentTime()) >= waitUntilSec
46 }) 49 })
47 50
48 await videojsElem().click() 51 await videojsElem().click()
diff --git a/client/e2e/src/po/video-list.po.ts b/client/e2e/src/po/video-list.po.ts
new file mode 100644
index 000000000..f62c79adc
--- /dev/null
+++ b/client/e2e/src/po/video-list.po.ts
@@ -0,0 +1,128 @@
1import { browserSleep, go } from '../utils'
2
3export class VideoListPage {
4
5 constructor (private isMobileDevice: boolean, private isSafari: boolean) {
6
7 }
8
9 async goOnVideosList () {
10 let url: string
11
12 // We did not upload a file on a mobile device
13 if (this.isMobileDevice === true || this.isSafari === true) {
14 url = 'https://peertube2.cpy.re/videos/local'
15 } else {
16 url = '/videos/recently-added'
17 }
18
19 await go(url)
20
21 // Waiting the following element does not work on Safari...
22 if (this.isSafari) return browserSleep(3000)
23
24 await this.waitForList()
25 }
26
27 async goOnLocal () {
28 await $('.menu-link[href="/videos/local"]').click()
29 await this.waitForTitle('Local videos')
30 }
31
32 async goOnRecentlyAdded () {
33 await $('.menu-link[href="/videos/recently-added"]').click()
34 await this.waitForTitle('Recently added')
35 }
36
37 async goOnTrending () {
38 await $('.menu-link[href="/videos/trending"]').click()
39 await this.waitForTitle('Trending')
40 }
41
42 async goOnHomepage () {
43 await go('/home')
44 await this.waitForList()
45 }
46
47 async goOnRootChannel () {
48 await go('/c/root_channel/videos')
49 await this.waitForList()
50 }
51
52 async goOnRootAccount () {
53 await go('/a/root/videos')
54 await this.waitForList()
55 }
56
57 async goOnRootAccountChannels () {
58 await go('/a/root/video-channels')
59 await this.waitForList()
60 }
61
62 getNSFWFilter () {
63 return $$('.active-filter').filter(async a => {
64 return (await a.getText()).includes('Sensitive')
65 }).then(f => f[0])
66 }
67
68 async getVideosListName () {
69 const elems = await $$('.videos .video-miniature .video-miniature-name')
70 const texts = await Promise.all(elems.map(e => e.getText()))
71
72 return texts.map(t => t.trim())
73 }
74
75 videoExists (name: string) {
76 return $('.video-miniature-name=' + name).isDisplayed()
77 }
78
79 async videoIsBlurred (name: string) {
80 const filter = await $('.video-miniature-name=' + name).getCSSProperty('filter')
81
82 return filter.value !== 'none'
83 }
84
85 async clickOnVideo (videoName: string) {
86 const video = async () => {
87 const videos = await $$('.videos .video-miniature .video-miniature-name').filter(async e => {
88 const t = await e.getText()
89
90 return t === videoName
91 })
92
93 return videos[0]
94 }
95
96 await browser.waitUntil(async () => {
97 const elem = await video()
98
99 return elem?.isClickable()
100 });
101
102 (await video()).click()
103
104 await browser.waitUntil(async () => (await browser.getUrl()).includes('/w/'))
105 }
106
107 async clickOnFirstVideo () {
108 const video = () => $('.videos .video-miniature .video-thumbnail')
109 const videoName = () => $('.videos .video-miniature .video-miniature-name')
110
111 await video().waitForClickable()
112
113 const textToReturn = await videoName().getText()
114 await video().click()
115
116 await browser.waitUntil(async () => (await browser.getUrl()).includes('/w/'))
117
118 return textToReturn
119 }
120
121 private waitForList () {
122 return $('.videos .video-miniature .video-miniature-name').waitForDisplayed()
123 }
124
125 private waitForTitle (title: string) {
126 return $('h1=' + title).waitForDisplayed()
127 }
128}
diff --git a/client/e2e/src/po/video-search.po.ts b/client/e2e/src/po/video-search.po.ts
new file mode 100644
index 000000000..5446718d1
--- /dev/null
+++ b/client/e2e/src/po/video-search.po.ts
@@ -0,0 +1,11 @@
1export class VideoSearchPage {
2
3 async search (search: string) {
4 await $('#search-video').setValue(search)
5 await $('my-header .icon-search').click()
6
7 await browser.waitUntil(() => {
8 return $('my-video-miniature').isDisplayed()
9 })
10 }
11}
diff --git a/client/e2e/src/po/video-upload.po.ts b/client/e2e/src/po/video-upload.po.ts
index 34f916b55..dd437c390 100644
--- a/client/e2e/src/po/video-upload.po.ts
+++ b/client/e2e/src/po/video-upload.po.ts
@@ -1,4 +1,5 @@
1import { join } from 'path' 1import { join } from 'path'
2import { clickOnCheckbox } from '../utils'
2 3
3export class VideoUploadPage { 4export class VideoUploadPage {
4 async navigateTo () { 5 async navigateTo () {
@@ -30,6 +31,10 @@ export class VideoUploadPage {
30 }) 31 })
31 } 32 }
32 33
34 setAsNSFW () {
35 return clickOnCheckbox('nsfw')
36 }
37
33 async validSecondUploadStep (videoName: string) { 38 async validSecondUploadStep (videoName: string) {
34 const nameInput = $('input#name') 39 const nameInput = $('input#name')
35 await nameInput.clearValue() 40 await nameInput.clearValue()
diff --git a/client/e2e/src/po/video-watch.po.ts b/client/e2e/src/po/video-watch.po.ts
index c07f4b25f..41425f4d7 100644
--- a/client/e2e/src/po/video-watch.po.ts
+++ b/client/e2e/src/po/video-watch.po.ts
@@ -1,37 +1,16 @@
1import { FIXTURE_URLS } from '../urls' 1import { browserSleep, FIXTURE_URLS, go } from '../utils'
2import { browserSleep, go } from '../utils'
3 2
4export class VideoWatchPage { 3export class VideoWatchPage {
5 async goOnVideosList (isMobileDevice: boolean, isSafari: boolean) {
6 let url: string
7
8 // We did not upload a file on a mobile device
9 if (isMobileDevice === true || isSafari === true) {
10 url = 'https://peertube2.cpy.re/videos/local'
11 } else {
12 url = '/videos/recently-added'
13 }
14
15 await go(url)
16
17 // Waiting the following element does not work on Safari...
18 if (isSafari) return browserSleep(3000)
19 4
20 await $('.videos .video-miniature .video-miniature-name').waitForDisplayed() 5 constructor (private isMobileDevice: boolean, private isSafari: boolean) {
21 }
22
23 async getVideosListName () {
24 const elems = await $$('.videos .video-miniature .video-miniature-name')
25 const texts = await Promise.all(elems.map(e => e.getText()))
26 6
27 return texts.map(t => t.trim())
28 } 7 }
29 8
30 waitWatchVideoName (videoName: string, isMobileDevice: boolean, isSafari: boolean) { 9 waitWatchVideoName (videoName: string) {
31 if (isSafari) return browserSleep(5000) 10 if (this.isSafari) return browserSleep(5000)
32 11
33 // On mobile we display the first node, on desktop the second 12 // On mobile we display the first node, on desktop the second
34 const index = isMobileDevice ? 0 : 1 13 const index = this.isMobileDevice ? 0 : 1
35 14
36 return browser.waitUntil(async () => { 15 return browser.waitUntil(async () => {
37 return (await $$('.video-info .video-info-name')[index].getText()).includes(videoName) 16 return (await $$('.video-info .video-info-name')[index].getText()).includes(videoName)
@@ -58,42 +37,6 @@ export class VideoWatchPage {
58 return go(FIXTURE_URLS.HLS_PLAYLIST_EMBED) 37 return go(FIXTURE_URLS.HLS_PLAYLIST_EMBED)
59 } 38 }
60 39
61 async clickOnVideo (videoName: string) {
62 const video = async () => {
63 const videos = await $$('.videos .video-miniature .video-miniature-name').filter(async e => {
64 const t = await e.getText()
65
66 return t === videoName
67 })
68
69 return videos[0]
70 }
71
72 await browser.waitUntil(async () => {
73 const elem = await video()
74
75 return elem?.isClickable()
76 });
77
78 (await video()).click()
79
80 await browser.waitUntil(async () => (await browser.getUrl()).includes('/w/'))
81 }
82
83 async clickOnFirstVideo () {
84 const video = () => $('.videos .video-miniature .video-thumbnail')
85 const videoName = () => $('.videos .video-miniature .video-miniature-name')
86
87 await video().waitForClickable()
88
89 const textToReturn = await videoName().getText()
90 await video().click()
91
92 await browser.waitUntil(async () => (await browser.getUrl()).includes('/w/'))
93
94 return textToReturn
95 }
96
97 async clickOnUpdate () { 40 async clickOnUpdate () {
98 const dropdown = $('my-video-actions-dropdown .action-button') 41 const dropdown = $('my-video-actions-dropdown .action-button')
99 await dropdown.click() 42 await dropdown.click()
diff --git a/client/e2e/src/videos.e2e-spec.ts b/client/e2e/src/suites-all/videos.e2e-spec.ts
index e09e8c675..3b8305a25 100644
--- a/client/e2e/src/videos.e2e-spec.ts
+++ b/client/e2e/src/suites-all/videos.e2e-spec.ts
@@ -1,11 +1,11 @@
1import { LoginPage } from './po/login.po' 1import { LoginPage } from '../po/login.po'
2import { MyAccountPage } from './po/my-account' 2import { MyAccountPage } from '../po/my-account'
3import { PlayerPage } from './po/player.po' 3import { PlayerPage } from '../po/player.po'
4import { VideoUpdatePage } from './po/video-update.po' 4import { VideoListPage } from '../po/video-list.po'
5import { VideoUploadPage } from './po/video-upload.po' 5import { VideoUpdatePage } from '../po/video-update.po'
6import { VideoWatchPage } from './po/video-watch.po' 6import { VideoUploadPage } from '../po/video-upload.po'
7import { FIXTURE_URLS } from './urls' 7import { VideoWatchPage } from '../po/video-watch.po'
8import { browserSleep, go, isIOS, isMobileDevice, isSafari } from './utils' 8import { FIXTURE_URLS, go, isIOS, isMobileDevice, isSafari, waitServerUp } from '../utils'
9 9
10function isUploadUnsupported () { 10function isUploadUnsupported () {
11 if (isMobileDevice() || isSafari()) { 11 if (isMobileDevice() || isSafari()) {
@@ -16,8 +16,9 @@ function isUploadUnsupported () {
16 return false 16 return false
17} 17}
18 18
19describe('Videos workflow', () => { 19describe('Videos all workflow', () => {
20 let videoWatchPage: VideoWatchPage 20 let videoWatchPage: VideoWatchPage
21 let videoListPage: VideoListPage
21 let videoUploadPage: VideoUploadPage 22 let videoUploadPage: VideoUploadPage
22 let videoUpdatePage: VideoUpdatePage 23 let videoUpdatePage: VideoUpdatePage
23 let myAccountPage: MyAccountPage 24 let myAccountPage: MyAccountPage
@@ -40,21 +41,17 @@ describe('Videos workflow', () => {
40 41
41 if (isUploadUnsupported()) return 42 if (isUploadUnsupported()) return
42 43
43 await browser.waitUntil(async () => { 44 await waitServerUp()
44 await go('/')
45 await browserSleep(500)
46
47 return $('<my-app>').isDisplayed()
48 }, { timeout: 20 * 1000 })
49 }) 45 })
50 46
51 beforeEach(async () => { 47 beforeEach(async () => {
52 videoWatchPage = new VideoWatchPage() 48 videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
53 videoUploadPage = new VideoUploadPage() 49 videoUploadPage = new VideoUploadPage()
54 videoUpdatePage = new VideoUpdatePage() 50 videoUpdatePage = new VideoUpdatePage()
55 myAccountPage = new MyAccountPage() 51 myAccountPage = new MyAccountPage()
56 loginPage = new LoginPage() 52 loginPage = new LoginPage()
57 playerPage = new PlayerPage() 53 playerPage = new PlayerPage()
54 videoListPage = new VideoListPage(isMobileDevice(), isSafari())
58 55
59 if (!isMobileDevice()) { 56 if (!isMobileDevice()) {
60 await browser.maximizeWindow() 57 await browser.maximizeWindow()
@@ -80,11 +77,11 @@ describe('Videos workflow', () => {
80 }) 77 })
81 78
82 it('Should list videos', async () => { 79 it('Should list videos', async () => {
83 await videoWatchPage.goOnVideosList(isMobileDevice(), isSafari()) 80 await videoListPage.goOnVideosList()
84 81
85 if (isUploadUnsupported()) return 82 if (isUploadUnsupported()) return
86 83
87 const videoNames = await videoWatchPage.getVideosListName() 84 const videoNames = await videoListPage.getVideosListName()
88 expect(videoNames).toContain(videoName) 85 expect(videoNames).toContain(videoName)
89 }) 86 })
90 87
@@ -95,10 +92,10 @@ describe('Videos workflow', () => {
95 await go(FIXTURE_URLS.WEBTORRENT_VIDEO) 92 await go(FIXTURE_URLS.WEBTORRENT_VIDEO)
96 videoNameToExcept = 'E2E tests' 93 videoNameToExcept = 'E2E tests'
97 } else { 94 } else {
98 await videoWatchPage.clickOnVideo(videoName) 95 await videoListPage.clickOnVideo(videoName)
99 } 96 }
100 97
101 return videoWatchPage.waitWatchVideoName(videoNameToExcept, isMobileDevice(), isSafari()) 98 return videoWatchPage.waitWatchVideoName(videoNameToExcept)
102 }) 99 })
103 100
104 it('Should play the video', async () => { 101 it('Should play the video', async () => {
diff --git a/client/e2e/src/suites-local/videos-list.e2e-spec.ts b/client/e2e/src/suites-local/videos-list.e2e-spec.ts
new file mode 100644
index 000000000..1e0a88859
--- /dev/null
+++ b/client/e2e/src/suites-local/videos-list.e2e-spec.ts
@@ -0,0 +1,195 @@
1import { AdminConfigPage } from '../po/admin-config.po'
2import { LoginPage } from '../po/login.po'
3import { MyAccountPage } from '../po/my-account'
4import { VideoListPage } from '../po/video-list.po'
5import { VideoSearchPage } from '../po/video-search.po'
6import { VideoUploadPage } from '../po/video-upload.po'
7import { NSFWPolicy } from '../types/common'
8import { isMobileDevice, isSafari, waitServerUp } from '../utils'
9
10describe('Videos list', () => {
11 let videoListPage: VideoListPage
12 let videoUploadPage: VideoUploadPage
13 let adminConfigPage: AdminConfigPage
14 let loginPage: LoginPage
15 let myAccountPage: MyAccountPage
16 let videoSearchPage: VideoSearchPage
17
18 const seed = Math.random()
19 const nsfwVideo = seed + ' - nsfw'
20 const normalVideo = seed + ' - normal'
21
22 async function checkNormalVideo () {
23 expect(await videoListPage.videoExists(normalVideo)).toBeTruthy()
24 expect(await videoListPage.videoIsBlurred(normalVideo)).toBeFalsy()
25 }
26
27 async function checkNSFWVideo (policy: NSFWPolicy, filterText?: string) {
28 if (policy === 'do_not_list') {
29 if (filterText) expect(filterText).toContain('hidden')
30
31 expect(await videoListPage.videoExists(nsfwVideo)).toBeFalsy()
32 return
33 }
34
35 if (policy === 'blur') {
36 if (filterText) expect(filterText).toContain('blurred')
37
38 expect(await videoListPage.videoExists(nsfwVideo)).toBeTruthy()
39 expect(await videoListPage.videoIsBlurred(nsfwVideo)).toBeTruthy()
40 return
41 }
42
43 // display
44 if (filterText) expect(filterText).toContain('displayed')
45
46 expect(await videoListPage.videoExists(nsfwVideo)).toBeTruthy()
47 expect(await videoListPage.videoIsBlurred(nsfwVideo)).toBeFalsy()
48 }
49
50 async function checkCommonVideoListPages (policy: NSFWPolicy) {
51 const promisesWithFilters = [
52 videoListPage.goOnRootAccount,
53 videoListPage.goOnLocal,
54 videoListPage.goOnRecentlyAdded,
55 videoListPage.goOnTrending,
56 videoListPage.goOnRootChannel
57 ]
58
59 for (const p of promisesWithFilters) {
60 await p.call(videoListPage)
61
62 const filter = await videoListPage.getNSFWFilter()
63 const filterText = await filter.getText()
64
65 await checkNormalVideo()
66 await checkNSFWVideo(policy, filterText)
67 }
68
69 const promisesWithoutFilters = [
70 videoListPage.goOnRootAccountChannels,
71 videoListPage.goOnHomepage
72 ]
73 for (const p of promisesWithoutFilters) {
74 await p.call(videoListPage)
75
76 await checkNormalVideo()
77 await checkNSFWVideo(policy)
78 }
79 }
80
81 async function checkSearchPage (policy: NSFWPolicy) {
82 await videoSearchPage.search(normalVideo)
83 await checkNormalVideo()
84
85 await videoSearchPage.search(nsfwVideo)
86 await checkNSFWVideo(policy)
87 }
88
89 async function updateAdminNSFW (nsfw: NSFWPolicy) {
90 await adminConfigPage.navigateTo('instance-information')
91 await adminConfigPage.updateNSFWSetting(nsfw)
92 await adminConfigPage.save()
93 }
94
95 async function updateUserNSFW (nsfw: NSFWPolicy) {
96 await myAccountPage.navigateToMySettings()
97 await myAccountPage.updateNSFW(nsfw)
98 }
99
100 before(async () => {
101 await waitServerUp()
102 })
103
104 beforeEach(async () => {
105 videoListPage = new VideoListPage(isMobileDevice(), isSafari())
106 adminConfigPage = new AdminConfigPage()
107 loginPage = new LoginPage()
108 videoUploadPage = new VideoUploadPage()
109 myAccountPage = new MyAccountPage()
110 videoSearchPage = new VideoSearchPage()
111
112 await browser.maximizeWindow()
113 })
114
115 it('Should login and disable NSFW', async () => {
116 await loginPage.loginAsRootUser()
117 await updateUserNSFW('display')
118 })
119
120 it('Should set the homepage', async () => {
121 await adminConfigPage.navigateTo('instance-homepage')
122 await adminConfigPage.updateHomepage('<peertube-videos-list data-sort="-publishedAt"></peertube-videos-list>')
123 await adminConfigPage.save()
124 })
125
126 it('Should upload 2 videos (NSFW and classic videos)', async () => {
127 await videoUploadPage.navigateTo()
128 await videoUploadPage.uploadVideo()
129 await videoUploadPage.setAsNSFW()
130 await videoUploadPage.validSecondUploadStep(nsfwVideo)
131
132 await videoUploadPage.navigateTo()
133 await videoUploadPage.uploadVideo()
134 await videoUploadPage.validSecondUploadStep(normalVideo)
135 })
136
137 it('Should logout', async function () {
138 await loginPage.logout()
139 })
140
141 describe('Anonymous users', function () {
142
143 it('Should correctly handle do not list', async () => {
144 await loginPage.loginAsRootUser()
145 await updateAdminNSFW('do_not_list')
146
147 await loginPage.logout()
148 await checkCommonVideoListPages('do_not_list')
149 await checkSearchPage('do_not_list')
150 })
151
152 it('Should correctly handle blur', async () => {
153 await loginPage.loginAsRootUser()
154 await updateAdminNSFW('blur')
155
156 await loginPage.logout()
157 await checkCommonVideoListPages('blur')
158 await checkSearchPage('blur')
159 })
160
161 it('Should correctly handle display', async () => {
162 await loginPage.loginAsRootUser()
163 await updateAdminNSFW('display')
164
165 await loginPage.logout()
166 await checkCommonVideoListPages('display')
167 await checkSearchPage('display')
168 })
169 })
170
171 describe('Logged in users', function () {
172
173 before(async () => {
174 await loginPage.loginAsRootUser()
175 })
176
177 it('Should correctly handle do not list', async () => {
178 await updateUserNSFW('do_not_list')
179 await checkCommonVideoListPages('do_not_list')
180 await checkSearchPage('do_not_list')
181 })
182
183 it('Should correctly handle blur', async () => {
184 await updateUserNSFW('blur')
185 await checkCommonVideoListPages('blur')
186 await checkSearchPage('blur')
187 })
188
189 it('Should correctly handle display', async () => {
190 await updateUserNSFW('display')
191 await checkCommonVideoListPages('display')
192 await checkSearchPage('display')
193 })
194 })
195})
diff --git a/client/e2e/src/types/common.ts b/client/e2e/src/types/common.ts
new file mode 100644
index 000000000..c0b59d297
--- /dev/null
+++ b/client/e2e/src/types/common.ts
@@ -0,0 +1 @@
export type NSFWPolicy = 'do_not_list' | 'blur' | 'display'
diff --git a/client/e2e/src/utils.ts b/client/e2e/src/utils/common.ts
index df1c29238..eb5f6b450 100644
--- a/client/e2e/src/utils.ts
+++ b/client/e2e/src/utils/common.ts
@@ -28,10 +28,20 @@ async function go (url: string) {
28 }) 28 })
29} 29}
30 30
31async function waitServerUp () {
32 await browser.waitUntil(async () => {
33 await go('/')
34 await browserSleep(500)
35
36 return $('<my-app>').isDisplayed()
37 }, { timeout: 20 * 1000 })
38}
39
31export { 40export {
32 isMobileDevice, 41 isMobileDevice,
33 isSafari, 42 isSafari,
34 isIOS, 43 isIOS,
44 waitServerUp,
35 go, 45 go,
36 browserSleep 46 browserSleep
37} 47}
diff --git a/client/e2e/src/utils/elements.ts b/client/e2e/src/utils/elements.ts
new file mode 100644
index 000000000..cadc46cce
--- /dev/null
+++ b/client/e2e/src/utils/elements.ts
@@ -0,0 +1,7 @@
1function clickOnCheckbox (name: string) {
2 return $(`my-peertube-checkbox[inputname=${name}] label`).click()
3}
4
5export {
6 clickOnCheckbox
7}
diff --git a/client/e2e/src/utils/index.ts b/client/e2e/src/utils/index.ts
new file mode 100644
index 000000000..5da1ad517
--- /dev/null
+++ b/client/e2e/src/utils/index.ts
@@ -0,0 +1,3 @@
1export * from './common'
2export * from './elements'
3export * from './urls'
diff --git a/client/e2e/src/urls.ts b/client/e2e/src/utils/urls.ts
index 664c65931..664c65931 100644
--- a/client/e2e/src/urls.ts
+++ b/client/e2e/src/utils/urls.ts
diff --git a/client/e2e/tsconfig.json b/client/e2e/tsconfig.json
index f42206621..c72e1ed4c 100644
--- a/client/e2e/tsconfig.json
+++ b/client/e2e/tsconfig.json
@@ -2,6 +2,8 @@
2 "extends": "../tsconfig.json", 2 "extends": "../tsconfig.json",
3 "compilerOptions": { 3 "compilerOptions": {
4 "outDir": "../out-tsc/app", 4 "outDir": "../out-tsc/app",
5 "noImplicitAny": false,
6 "esModuleInterop": true,
5 "module": "commonjs", 7 "module": "commonjs",
6 "target": "es5", 8 "target": "es5",
7 "types": [ 9 "types": [
diff --git a/client/e2e/wdio.browserstack.conf.ts b/client/e2e/wdio.browserstack.conf.ts
index af3a454fc..43614a862 100644
--- a/client/e2e/wdio.browserstack.conf.ts
+++ b/client/e2e/wdio.browserstack.conf.ts
@@ -26,14 +26,16 @@ function buildBStackDesktopOptions (sessionName: string, resolution?: string) {
26 } 26 }
27} 27}
28 28
29function buildBStackMobileOptions (sessionName: string, deviceName: string, osVersion: string) { 29function buildBStackMobileOptions (sessionName: string, deviceName: string, osVersion: string, appiumVersion?: string) {
30 return { 30 return {
31 'bstack:options': { 31 'bstack:options': {
32 ...buildMainOptions(sessionName), 32 ...buildMainOptions(sessionName),
33 33
34 realMobile: true, 34 realMobile: true,
35 osVersion, 35 osVersion,
36 deviceName 36 deviceName,
37
38 appiumVersion
37 } 39 }
38 } 40 }
39} 41}
@@ -84,7 +86,7 @@ module.exports = {
84 { 86 {
85 browserName: 'Safari', 87 browserName: 'Safari',
86 88
87 ...buildBStackMobileOptions('Safari iPhone', 'iPhone 8 Plus', '11') 89 ...buildBStackMobileOptions('Safari iPhone', 'iPhone SE', '11')
88 }, 90 },
89 { 91 {
90 browserName: 'Safari', 92 browserName: 'Safari',
@@ -97,17 +99,20 @@ module.exports = {
97 connectionRetryTimeout: 240000, 99 connectionRetryTimeout: 240000,
98 waitforTimeout: 20000, 100 waitforTimeout: 20000,
99 101
102 specs: [
103 // We don't want to test "local" tests
104 './src/suites-all/*.e2e-spec.ts'
105 ],
106
100 services: [ 107 services: [
101 [ 108 [
102 'browserstack', { browserstackLocal: true } 109 'browserstack', { browserstackLocal: true }
103 ] 110 ]
104 ], 111 ],
105 112
106 after: function (result) { 113 onWorkerStart: function (_cid, capabilities) {
107 if (result === 0) { 114 if (capabilities['bstack:options'].realMobile === true) {
108 browser.executeScript('browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"passed","reason": ""}}', []) 115 capabilities['bstack:options'].local = false
109 } else {
110 browser.executeScript('browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"failed","reason": ""}}', [])
111 } 116 }
112 } 117 }
113 } as WebdriverIO.Config 118 } as WebdriverIO.Config
diff --git a/client/e2e/wdio.local-test.conf.ts b/client/e2e/wdio.local-test.conf.ts
new file mode 100644
index 000000000..6ae426322
--- /dev/null
+++ b/client/e2e/wdio.local-test.conf.ts
@@ -0,0 +1,28 @@
1import { config as mainConfig } from './wdio.main.conf'
2
3const prefs = {
4 'intl.accept_languages': 'en'
5}
6
7module.exports = {
8 config: {
9 ...mainConfig,
10
11 runner: 'local',
12
13 maxInstances: 1,
14 specFileRetries: 0,
15
16 capabilities: [
17 {
18 browserName: 'chrome',
19 acceptInsecureCerts: true,
20 'goog:chromeOptions': {
21 prefs
22 }
23 }
24 ],
25
26 services: [ 'chromedriver' ]
27 } as WebdriverIO.Config
28}
diff --git a/client/e2e/wdio.local.conf.ts b/client/e2e/wdio.local.conf.ts
index 4071aa249..a4c517f5e 100644
--- a/client/e2e/wdio.local.conf.ts
+++ b/client/e2e/wdio.local.conf.ts
@@ -10,12 +10,11 @@ module.exports = {
10 10
11 runner: 'local', 11 runner: 'local',
12 12
13 maxInstances: 1, 13 maxInstances: 2,
14 14
15 capabilities: [ 15 capabilities: [
16 { 16 {
17 browserName: 'chrome', 17 browserName: 'chrome',
18 acceptInsecureCerts: true,
19 'goog:chromeOptions': { 18 'goog:chromeOptions': {
20 prefs 19 prefs
21 } 20 }
@@ -23,21 +22,20 @@ module.exports = {
23 { 22 {
24 browserName: 'firefox', 23 browserName: 'firefox',
25 'moz:firefoxOptions': { 24 'moz:firefoxOptions': {
26 // args: [ '-headless' ],
27 binary: '/usr/bin/firefox-developer-edition', 25 binary: '/usr/bin/firefox-developer-edition',
28 prefs 26 prefs
29 } 27 }
30 },
31 {
32 browserName: 'firefox',
33 'moz:firefoxOptions': {
34 // args: [ '-headless' ],
35 binary: '/usr/bin/firefox-esr',
36 prefs
37 }
38 } 28 }
39 ], 29 ],
40 30
41 services: [ 'chromedriver', 'geckodriver' ] 31 services: [ 'chromedriver', 'geckodriver' ],
32
33 beforeSession: function (config, capabilities) {
34 if (capabilities['browserName'] === 'chrome') {
35 config.baseUrl = 'http://localhost:9001'
36 } else {
37 config.baseUrl = 'http://localhost:9002'
38 }
39 }
42 } as WebdriverIO.Config 40 } as WebdriverIO.Config
43} 41}
diff --git a/client/e2e/wdio.main.conf.ts b/client/e2e/wdio.main.conf.ts
index 7f4c7f7ee..29afdbdc0 100644
--- a/client/e2e/wdio.main.conf.ts
+++ b/client/e2e/wdio.main.conf.ts
@@ -21,7 +21,8 @@ export const config = {
21 // will be called from there. 21 // will be called from there.
22 // 22 //
23 specs: [ 23 specs: [
24 './src/**/*.e2e-spec.ts' 24 './src/suites-all/*.e2e-spec.ts',
25 './src/suites-local/*.e2e-spec.ts'
25 ], 26 ],
26 // Patterns to exclude. 27 // Patterns to exclude.
27 exclude: [ 28 exclude: [
@@ -79,7 +80,7 @@ export const config = {
79 framework: 'mocha', 80 framework: 'mocha',
80 // 81 //
81 // The number of times to retry the entire specfile when it fails as a whole 82 // The number of times to retry the entire specfile when it fails as a whole
82 specFileRetries: 2, 83 specFileRetries: 1,
83 // 84 //
84 // Delay in seconds between the spec file retry attempts 85 // Delay in seconds between the spec file retry attempts
85 // specFileRetriesDelay: 0, 86 // specFileRetriesDelay: 0,
@@ -105,6 +106,14 @@ export const config = {
105 106
106 tsNodeOpts: { 107 tsNodeOpts: {
107 project: require('path').join(__dirname, './tsconfig.json') 108 project: require('path').join(__dirname, './tsconfig.json')
109 },
110
111 tsConfigPathsOpts: {
112 baseUrl: './',
113 paths: {
114 '@server/*': [ '../../server/*' ],
115 '@shared/*': [ '../../shared/*' ]
116 }
108 } 117 }
109 }, 118 },
110 119
diff --git a/client/src/app/core/users/user.service.ts b/client/src/app/core/users/user.service.ts
index be9e66c3c..632361e9b 100644
--- a/client/src/app/core/users/user.service.ts
+++ b/client/src/app/core/users/user.service.ts
@@ -17,6 +17,7 @@ import {
17 UserUpdateMe, 17 UserUpdateMe,
18 UserVideoQuota 18 UserVideoQuota
19} from '@shared/models' 19} from '@shared/models'
20import { ServerService } from '../'
20import { environment } from '../../../environments/environment' 21import { environment } from '../../../environments/environment'
21import { RestExtractor, RestPagination, RestService } from '../rest' 22import { RestExtractor, RestPagination, RestService } from '../rest'
22import { LocalStorageService, SessionStorageService } from '../wrappers/storage.service' 23import { LocalStorageService, SessionStorageService } from '../wrappers/storage.service'
@@ -32,6 +33,7 @@ export class UserService {
32 33
33 constructor ( 34 constructor (
34 private authHttp: HttpClient, 35 private authHttp: HttpClient,
36 private server: ServerService,
35 private authService: AuthService, 37 private authService: AuthService,
36 private restExtractor: RestExtractor, 38 private restExtractor: RestExtractor,
37 private restService: RestService, 39 private restService: RestService,
@@ -298,9 +300,11 @@ export class UserService {
298 console.error('Cannot parse desired video languages from localStorage.', err) 300 console.error('Cannot parse desired video languages from localStorage.', err)
299 } 301 }
300 302
303 const defaultNSFWPolicy = this.server.getHTMLConfig().instance.defaultNSFWPolicy
304
301 return new User({ 305 return new User({
302 // local storage keys 306 // local storage keys
303 nsfwPolicy: this.localStorageService.getItem(UserLocalStorageKeys.NSFW_POLICY), 307 nsfwPolicy: this.localStorageService.getItem(UserLocalStorageKeys.NSFW_POLICY) || defaultNSFWPolicy,
304 webTorrentEnabled: this.localStorageService.getItem(UserLocalStorageKeys.WEBTORRENT_ENABLED) !== 'false', 308 webTorrentEnabled: this.localStorageService.getItem(UserLocalStorageKeys.WEBTORRENT_ENABLED) !== 'false',
305 theme: this.localStorageService.getItem(UserLocalStorageKeys.THEME) || 'instance-default', 309 theme: this.localStorageService.getItem(UserLocalStorageKeys.THEME) || 'instance-default',
306 videoLanguages, 310 videoLanguages,
diff --git a/client/src/app/shared/shared-video-miniature/video-filters-header.component.ts b/client/src/app/shared/shared-video-miniature/video-filters-header.component.ts
index 54a7c5dab..2c52e43f7 100644
--- a/client/src/app/shared/shared-video-miniature/video-filters-header.component.ts
+++ b/client/src/app/shared/shared-video-miniature/video-filters-header.component.ts
@@ -5,7 +5,6 @@ import { FormBuilder, FormGroup } from '@angular/forms'
5import { AuthService } from '@app/core' 5import { AuthService } from '@app/core'
6import { ServerService } from '@app/core/server/server.service' 6import { ServerService } from '@app/core/server/server.service'
7import { UserRight } from '@shared/models' 7import { UserRight } from '@shared/models'
8import { NSFWPolicyType } from '@shared/models/videos'
9import { PeertubeModalService } from '../shared-main' 8import { PeertubeModalService } from '../shared-main'
10import { VideoFilters } from './video-filters.model' 9import { VideoFilters } from './video-filters.model'
11 10
@@ -18,12 +17,7 @@ const logger = debug('peertube:videos:VideoFiltersHeaderComponent')
18}) 17})
19export class VideoFiltersHeaderComponent implements OnInit, OnDestroy { 18export class VideoFiltersHeaderComponent implements OnInit, OnDestroy {
20 @Input() filters: VideoFilters 19 @Input() filters: VideoFilters
21
22 @Input() displayModerationBlock = false 20 @Input() displayModerationBlock = false
23
24 @Input() defaultSort = '-publishedAt'
25 @Input() nsfwPolicy: NSFWPolicyType
26
27 @Input() hideScope = false 21 @Input() hideScope = false
28 22
29 @Output() filtersChanged = new EventEmitter() 23 @Output() filtersChanged = new EventEmitter()
diff --git a/client/src/app/shared/shared-video-miniature/video-filters.model.ts b/client/src/app/shared/shared-video-miniature/video-filters.model.ts
index 920dc826c..f5095b85b 100644
--- a/client/src/app/shared/shared-video-miniature/video-filters.model.ts
+++ b/client/src/app/shared/shared-video-miniature/video-filters.model.ts
@@ -74,6 +74,8 @@ export class VideoFilters {
74 } 74 }
75 75
76 setNSFWPolicy (nsfwPolicy: NSFWPolicyType) { 76 setNSFWPolicy (nsfwPolicy: NSFWPolicyType) {
77 console.log(nsfwPolicy)
78
77 this.updateDefaultNSFW(nsfwPolicy) 79 this.updateDefaultNSFW(nsfwPolicy)
78 } 80 }
79 81
diff --git a/client/src/app/shared/shared-video-miniature/videos-list.component.html b/client/src/app/shared/shared-video-miniature/videos-list.component.html
index 08ed7b94c..67933f177 100644
--- a/client/src/app/shared/shared-video-miniature/videos-list.component.html
+++ b/client/src/app/shared/shared-video-miniature/videos-list.component.html
@@ -34,7 +34,7 @@
34 34
35 <my-video-filters-header 35 <my-video-filters-header
36 *ngIf="displayFilters" [displayModerationBlock]="displayModerationBlock" [hideScope]="hideScopeFilter" 36 *ngIf="displayFilters" [displayModerationBlock]="displayModerationBlock" [hideScope]="hideScopeFilter"
37 [defaultSort]="defaultSort" [filters]="filters" 37 [filters]="filters"
38 (filtersChanged)="onFiltersChanged(true)" 38 (filtersChanged)="onFiltersChanged(true)"
39 ></my-video-filters-header> 39 ></my-video-filters-header>
40 40
diff --git a/scripts/e2e/browserstack.sh b/scripts/e2e/browserstack.sh
index 69a12d14c..fb125ea23 100755
--- a/scripts/e2e/browserstack.sh
+++ b/scripts/e2e/browserstack.sh
@@ -6,4 +6,4 @@ npm run clean:server:test
6 6
7npm run concurrently -- -k -s first \ 7npm run concurrently -- -k -s first \
8 "cd client/e2e && ../node_modules/.bin/wdio run ./wdio.browserstack.conf.ts" \ 8 "cd client/e2e && ../node_modules/.bin/wdio run ./wdio.browserstack.conf.ts" \
9 "NODE_ENV=test NODE_APP_INSTANCE=1 NODE_CONFIG='{ \"log\": { \"level\": \"warn\" }, \"signup\": { \"enabled\": false } }' node dist/server" 9 "NODE_ENV=test NODE_APP_INSTANCE=1 NODE_CONFIG='{ \"rates_limit\": { \"api\": { \"max\": 5000 }, \"login\": { \"max\": 5000 } }, \"log\": { \"level\": \"warn\" }, \"signup\": { \"enabled\": false } }' node dist/server"
diff --git a/scripts/e2e/local.sh b/scripts/e2e/local.sh
index bbbdb5f0b..fe8b7f559 100755
--- a/scripts/e2e/local.sh
+++ b/scripts/e2e/local.sh
@@ -4,6 +4,14 @@ set -eu
4 4
5npm run clean:server:test 5npm run clean:server:test
6 6
7config="{"
8config+=" \"rates_limit\": { \"api\": { \"max\": 5000 }, \"login\": { \"max\": 5000 } }"
9config+=", \"log\": { \"level\": \"warn\" }"
10config+=", \"signup\": { \"enabled\": false }"
11config+=", \"transcoding\": { \"enabled\": false }"
12config+="}"
13
7npm run concurrently -- -k -s first \ 14npm run concurrently -- -k -s first \
8 "cd client/e2e && ../node_modules/.bin/wdio run ./wdio.local.conf.ts" \ 15 "cd client/e2e && ../node_modules/.bin/wdio run ./wdio.local.conf.ts" \
9 "NODE_ENV=test NODE_APP_INSTANCE=1 NODE_CONFIG='{ \"log\": { \"level\": \"warn\" }, \"signup\": { \"enabled\": false } }' node dist/server" 16 "NODE_ENV=test NODE_CONFIG='$config' NODE_APP_INSTANCE=1 node dist/server" \
17 "NODE_ENV=test NODE_CONFIG='$config' NODE_APP_INSTANCE=2 node dist/server"
diff --git a/support/doc/development/tests.md b/support/doc/development/tests.md
index e311d3267..fb3a05326 100644
--- a/support/doc/development/tests.md
+++ b/support/doc/development/tests.md
@@ -70,3 +70,18 @@ To run tests on browser stack:
70``` 70```
71$ BROWSERSTACK_USER=your_user BROWSERSTACK_KEY=your_key npm run e2e:browserstack 71$ BROWSERSTACK_USER=your_user BROWSERSTACK_KEY=your_key npm run e2e:browserstack
72``` 72```
73
74### Add E2E tests
75
76To add E2E tests and quickly run tests using a local Chrome, first create a test instance:
77
78```
79$ npm run clean:server:test && NODE_APP_INSTANCE=1 NODE_ENV=test npm start
80```
81
82Then, just run your suite using:
83
84```
85$ cd client/e2e
86$ ../node_modules/.bin/wdio wdio.local-test.conf.ts # you can also add --mochaOpts.grep to only run tests you want
87```