From 6d210220be0875d63461829d83c6e3a59d05cf7a Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 3 Sep 2021 10:27:04 +0200 Subject: [PATCH] Fix NSFW filter and add tests --- client/angular.json | 1 + client/e2e/src/po/admin-config.po.ts | 29 +++ client/e2e/src/po/login.po.ts | 20 +- client/e2e/src/po/my-account.ts | 18 ++ client/e2e/src/po/player.po.ts | 5 +- client/e2e/src/po/video-list.po.ts | 128 ++++++++++++ client/e2e/src/po/video-search.po.ts | 11 + client/e2e/src/po/video-upload.po.ts | 5 + client/e2e/src/po/video-watch.po.ts | 67 +----- .../src/{ => suites-all}/videos.e2e-spec.ts | 37 ++-- .../src/suites-local/videos-list.e2e-spec.ts | 195 ++++++++++++++++++ client/e2e/src/types/common.ts | 1 + client/e2e/src/{utils.ts => utils/common.ts} | 10 + client/e2e/src/utils/elements.ts | 7 + client/e2e/src/utils/index.ts | 3 + client/e2e/src/{ => utils}/urls.ts | 0 client/e2e/tsconfig.json | 2 + client/e2e/wdio.browserstack.conf.ts | 21 +- client/e2e/wdio.local-test.conf.ts | 28 +++ client/e2e/wdio.local.conf.ts | 22 +- client/e2e/wdio.main.conf.ts | 13 +- client/src/app/core/users/user.service.ts | 6 +- .../video-filters-header.component.ts | 6 - .../video-filters.model.ts | 2 + .../videos-list.component.html | 2 +- scripts/e2e/browserstack.sh | 2 +- scripts/e2e/local.sh | 10 +- support/doc/development/tests.md | 15 ++ 28 files changed, 550 insertions(+), 116 deletions(-) create mode 100644 client/e2e/src/po/admin-config.po.ts create mode 100644 client/e2e/src/po/video-list.po.ts create mode 100644 client/e2e/src/po/video-search.po.ts rename client/e2e/src/{ => suites-all}/videos.e2e-spec.ts (86%) create mode 100644 client/e2e/src/suites-local/videos-list.e2e-spec.ts create mode 100644 client/e2e/src/types/common.ts rename client/e2e/src/{utils.ts => utils/common.ts} (80%) create mode 100644 client/e2e/src/utils/elements.ts create mode 100644 client/e2e/src/utils/index.ts rename client/e2e/src/{ => utils}/urls.ts (100%) create mode 100644 client/e2e/wdio.local-test.conf.ts 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 @@ "builder": "@angular-eslint/builder:lint", "options": { "lintFilePatterns": [ + "e2e/**/*.ts", "src/**/*.ts", "src/**/*.html" ] 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 @@ +import { browserSleep, go } from '../utils' + +export class AdminConfigPage { + + async navigateTo (tab: 'instance-homepage' | 'basic-configuration' | 'instance-information') { + const waitTitles = { + 'instance-homepage': 'INSTANCE HOMEPAGE', + 'basic-configuration': 'APPEARANCE', + 'instance-information': 'INSTANCE' + } + + await go('/admin/config/edit-custom#' + tab) + + await $('.inner-form-title=' + waitTitles[tab]).waitForDisplayed() + } + + updateNSFWSetting (newValue: 'do_not_list' | 'blur' | 'display') { + return $('#instanceDefaultNSFWPolicy').selectByAttribute('value', newValue) + } + + updateHomepage (newValue: string) { + return $('#instanceCustomHomepageContent').setValue(newValue) + } + + async save () { + await $('input[type=submit]').click() + await browserSleep(200) + } +} 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 @@ import { go } from '../utils' export class LoginPage { + async loginAsRootUser () { await go('/login') @@ -8,7 +9,7 @@ export class LoginPage { await browser.execute(`window.localStorage.setItem('no_welcome_modal', 'true')`) await $('input#username').setValue('root') - await $('input#password').setValue('test1') + await $('input#password').setValue('test' + this.getSuffix()) await browser.pause(1000) @@ -19,7 +20,24 @@ export class LoginPage { await expect(this.getLoggedInInfoElem()).toHaveText('root') } + async logout () { + await $('.logged-in-more').click() + + const logout = () => $('.dropdown-item*=Log out') + + await logout().waitForDisplayed() + await logout().click() + + await $('.login-buttons-block').waitForDisplayed() + } + private getLoggedInInfoElem () { return $('.logged-in-display-name') } + + private getSuffix () { + return browser.config.baseUrl + ? browser.config.baseUrl.slice(-1) + : '1' + } } 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 { return $('a[href="/my-library/history/videos"]').click() } + // Settings + + navigateToMySettings () { + return $('a[href="/my-account"]').click() + } + + async updateNSFW (newValue: 'do_not_list' | 'blur' | 'display') { + const nsfw = $('#nsfwPolicy') + + await nsfw.waitForDisplayed() + await nsfw.scrollIntoView(false) // Avoid issues with fixed header on firefox + await nsfw.selectByAttribute('value', newValue) + + const submit = $('my-user-video-settings input[type=submit]') + await submit.scrollIntoView(false) + await submit.click() + } + // My account Videos 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 { waitUntilPlaylistInfo (text: string, maxTime: number) { return browser.waitUntil(async () => { + // Without this we have issues on iphone + await $('.video-js').click() + return (await $('.video-js .vjs-playlist-info').getText()).includes(text) }, { timeout: maxTime }) } @@ -42,7 +45,7 @@ export class PlayerPage { await browserSleep(2000) await browser.waitUntil(async () => { - return (await this.getWatchVideoPlayerCurrentTime()) >= 2 + return (await this.getWatchVideoPlayerCurrentTime()) >= waitUntilSec }) 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 @@ +import { browserSleep, go } from '../utils' + +export class VideoListPage { + + constructor (private isMobileDevice: boolean, private isSafari: boolean) { + + } + + async goOnVideosList () { + let url: string + + // We did not upload a file on a mobile device + if (this.isMobileDevice === true || this.isSafari === true) { + url = 'https://peertube2.cpy.re/videos/local' + } else { + url = '/videos/recently-added' + } + + await go(url) + + // Waiting the following element does not work on Safari... + if (this.isSafari) return browserSleep(3000) + + await this.waitForList() + } + + async goOnLocal () { + await $('.menu-link[href="/videos/local"]').click() + await this.waitForTitle('Local videos') + } + + async goOnRecentlyAdded () { + await $('.menu-link[href="/videos/recently-added"]').click() + await this.waitForTitle('Recently added') + } + + async goOnTrending () { + await $('.menu-link[href="/videos/trending"]').click() + await this.waitForTitle('Trending') + } + + async goOnHomepage () { + await go('/home') + await this.waitForList() + } + + async goOnRootChannel () { + await go('/c/root_channel/videos') + await this.waitForList() + } + + async goOnRootAccount () { + await go('/a/root/videos') + await this.waitForList() + } + + async goOnRootAccountChannels () { + await go('/a/root/video-channels') + await this.waitForList() + } + + getNSFWFilter () { + return $$('.active-filter').filter(async a => { + return (await a.getText()).includes('Sensitive') + }).then(f => f[0]) + } + + async getVideosListName () { + const elems = await $$('.videos .video-miniature .video-miniature-name') + const texts = await Promise.all(elems.map(e => e.getText())) + + return texts.map(t => t.trim()) + } + + videoExists (name: string) { + return $('.video-miniature-name=' + name).isDisplayed() + } + + async videoIsBlurred (name: string) { + const filter = await $('.video-miniature-name=' + name).getCSSProperty('filter') + + return filter.value !== 'none' + } + + async clickOnVideo (videoName: string) { + const video = async () => { + const videos = await $$('.videos .video-miniature .video-miniature-name').filter(async e => { + const t = await e.getText() + + return t === videoName + }) + + return videos[0] + } + + await browser.waitUntil(async () => { + const elem = await video() + + return elem?.isClickable() + }); + + (await video()).click() + + await browser.waitUntil(async () => (await browser.getUrl()).includes('/w/')) + } + + async clickOnFirstVideo () { + const video = () => $('.videos .video-miniature .video-thumbnail') + const videoName = () => $('.videos .video-miniature .video-miniature-name') + + await video().waitForClickable() + + const textToReturn = await videoName().getText() + await video().click() + + await browser.waitUntil(async () => (await browser.getUrl()).includes('/w/')) + + return textToReturn + } + + private waitForList () { + return $('.videos .video-miniature .video-miniature-name').waitForDisplayed() + } + + private waitForTitle (title: string) { + return $('h1=' + title).waitForDisplayed() + } +} 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 @@ +export class VideoSearchPage { + + async search (search: string) { + await $('#search-video').setValue(search) + await $('my-header .icon-search').click() + + await browser.waitUntil(() => { + return $('my-video-miniature').isDisplayed() + }) + } +} 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 @@ import { join } from 'path' +import { clickOnCheckbox } from '../utils' export class VideoUploadPage { async navigateTo () { @@ -30,6 +31,10 @@ export class VideoUploadPage { }) } + setAsNSFW () { + return clickOnCheckbox('nsfw') + } + async validSecondUploadStep (videoName: string) { const nameInput = $('input#name') 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 @@ -import { FIXTURE_URLS } from '../urls' -import { browserSleep, go } from '../utils' +import { browserSleep, FIXTURE_URLS, go } from '../utils' export class VideoWatchPage { - async goOnVideosList (isMobileDevice: boolean, isSafari: boolean) { - let url: string - // We did not upload a file on a mobile device - if (isMobileDevice === true || isSafari === true) { - url = 'https://peertube2.cpy.re/videos/local' - } else { - url = '/videos/recently-added' - } + constructor (private isMobileDevice: boolean, private isSafari: boolean) { - await go(url) - - // Waiting the following element does not work on Safari... - if (isSafari) return browserSleep(3000) - - await $('.videos .video-miniature .video-miniature-name').waitForDisplayed() } - async getVideosListName () { - const elems = await $$('.videos .video-miniature .video-miniature-name') - const texts = await Promise.all(elems.map(e => e.getText())) - - return texts.map(t => t.trim()) - } - - waitWatchVideoName (videoName: string, isMobileDevice: boolean, isSafari: boolean) { - if (isSafari) return browserSleep(5000) + waitWatchVideoName (videoName: string) { + if (this.isSafari) return browserSleep(5000) // On mobile we display the first node, on desktop the second - const index = isMobileDevice ? 0 : 1 + const index = this.isMobileDevice ? 0 : 1 return browser.waitUntil(async () => { return (await $$('.video-info .video-info-name')[index].getText()).includes(videoName) @@ -58,42 +37,6 @@ export class VideoWatchPage { return go(FIXTURE_URLS.HLS_PLAYLIST_EMBED) } - async clickOnVideo (videoName: string) { - const video = async () => { - const videos = await $$('.videos .video-miniature .video-miniature-name').filter(async e => { - const t = await e.getText() - - return t === videoName - }) - - return videos[0] - } - - await browser.waitUntil(async () => { - const elem = await video() - - return elem?.isClickable() - }); - - (await video()).click() - - await browser.waitUntil(async () => (await browser.getUrl()).includes('/w/')) - } - - async clickOnFirstVideo () { - const video = () => $('.videos .video-miniature .video-thumbnail') - const videoName = () => $('.videos .video-miniature .video-miniature-name') - - await video().waitForClickable() - - const textToReturn = await videoName().getText() - await video().click() - - await browser.waitUntil(async () => (await browser.getUrl()).includes('/w/')) - - return textToReturn - } - async clickOnUpdate () { const dropdown = $('my-video-actions-dropdown .action-button') await dropdown.click() diff --git a/client/e2e/src/videos.e2e-spec.ts b/client/e2e/src/suites-all/videos.e2e-spec.ts similarity index 86% rename from client/e2e/src/videos.e2e-spec.ts rename to 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 @@ -import { LoginPage } from './po/login.po' -import { MyAccountPage } from './po/my-account' -import { PlayerPage } from './po/player.po' -import { VideoUpdatePage } from './po/video-update.po' -import { VideoUploadPage } from './po/video-upload.po' -import { VideoWatchPage } from './po/video-watch.po' -import { FIXTURE_URLS } from './urls' -import { browserSleep, go, isIOS, isMobileDevice, isSafari } from './utils' +import { LoginPage } from '../po/login.po' +import { MyAccountPage } from '../po/my-account' +import { PlayerPage } from '../po/player.po' +import { VideoListPage } from '../po/video-list.po' +import { VideoUpdatePage } from '../po/video-update.po' +import { VideoUploadPage } from '../po/video-upload.po' +import { VideoWatchPage } from '../po/video-watch.po' +import { FIXTURE_URLS, go, isIOS, isMobileDevice, isSafari, waitServerUp } from '../utils' function isUploadUnsupported () { if (isMobileDevice() || isSafari()) { @@ -16,8 +16,9 @@ function isUploadUnsupported () { return false } -describe('Videos workflow', () => { +describe('Videos all workflow', () => { let videoWatchPage: VideoWatchPage + let videoListPage: VideoListPage let videoUploadPage: VideoUploadPage let videoUpdatePage: VideoUpdatePage let myAccountPage: MyAccountPage @@ -40,21 +41,17 @@ describe('Videos workflow', () => { if (isUploadUnsupported()) return - await browser.waitUntil(async () => { - await go('/') - await browserSleep(500) - - return $('').isDisplayed() - }, { timeout: 20 * 1000 }) + await waitServerUp() }) beforeEach(async () => { - videoWatchPage = new VideoWatchPage() + videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari()) videoUploadPage = new VideoUploadPage() videoUpdatePage = new VideoUpdatePage() myAccountPage = new MyAccountPage() loginPage = new LoginPage() playerPage = new PlayerPage() + videoListPage = new VideoListPage(isMobileDevice(), isSafari()) if (!isMobileDevice()) { await browser.maximizeWindow() @@ -80,11 +77,11 @@ describe('Videos workflow', () => { }) it('Should list videos', async () => { - await videoWatchPage.goOnVideosList(isMobileDevice(), isSafari()) + await videoListPage.goOnVideosList() if (isUploadUnsupported()) return - const videoNames = await videoWatchPage.getVideosListName() + const videoNames = await videoListPage.getVideosListName() expect(videoNames).toContain(videoName) }) @@ -95,10 +92,10 @@ describe('Videos workflow', () => { await go(FIXTURE_URLS.WEBTORRENT_VIDEO) videoNameToExcept = 'E2E tests' } else { - await videoWatchPage.clickOnVideo(videoName) + await videoListPage.clickOnVideo(videoName) } - return videoWatchPage.waitWatchVideoName(videoNameToExcept, isMobileDevice(), isSafari()) + return videoWatchPage.waitWatchVideoName(videoNameToExcept) }) 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 @@ +import { AdminConfigPage } from '../po/admin-config.po' +import { LoginPage } from '../po/login.po' +import { MyAccountPage } from '../po/my-account' +import { VideoListPage } from '../po/video-list.po' +import { VideoSearchPage } from '../po/video-search.po' +import { VideoUploadPage } from '../po/video-upload.po' +import { NSFWPolicy } from '../types/common' +import { isMobileDevice, isSafari, waitServerUp } from '../utils' + +describe('Videos list', () => { + let videoListPage: VideoListPage + let videoUploadPage: VideoUploadPage + let adminConfigPage: AdminConfigPage + let loginPage: LoginPage + let myAccountPage: MyAccountPage + let videoSearchPage: VideoSearchPage + + const seed = Math.random() + const nsfwVideo = seed + ' - nsfw' + const normalVideo = seed + ' - normal' + + async function checkNormalVideo () { + expect(await videoListPage.videoExists(normalVideo)).toBeTruthy() + expect(await videoListPage.videoIsBlurred(normalVideo)).toBeFalsy() + } + + async function checkNSFWVideo (policy: NSFWPolicy, filterText?: string) { + if (policy === 'do_not_list') { + if (filterText) expect(filterText).toContain('hidden') + + expect(await videoListPage.videoExists(nsfwVideo)).toBeFalsy() + return + } + + if (policy === 'blur') { + if (filterText) expect(filterText).toContain('blurred') + + expect(await videoListPage.videoExists(nsfwVideo)).toBeTruthy() + expect(await videoListPage.videoIsBlurred(nsfwVideo)).toBeTruthy() + return + } + + // display + if (filterText) expect(filterText).toContain('displayed') + + expect(await videoListPage.videoExists(nsfwVideo)).toBeTruthy() + expect(await videoListPage.videoIsBlurred(nsfwVideo)).toBeFalsy() + } + + async function checkCommonVideoListPages (policy: NSFWPolicy) { + const promisesWithFilters = [ + videoListPage.goOnRootAccount, + videoListPage.goOnLocal, + videoListPage.goOnRecentlyAdded, + videoListPage.goOnTrending, + videoListPage.goOnRootChannel + ] + + for (const p of promisesWithFilters) { + await p.call(videoListPage) + + const filter = await videoListPage.getNSFWFilter() + const filterText = await filter.getText() + + await checkNormalVideo() + await checkNSFWVideo(policy, filterText) + } + + const promisesWithoutFilters = [ + videoListPage.goOnRootAccountChannels, + videoListPage.goOnHomepage + ] + for (const p of promisesWithoutFilters) { + await p.call(videoListPage) + + await checkNormalVideo() + await checkNSFWVideo(policy) + } + } + + async function checkSearchPage (policy: NSFWPolicy) { + await videoSearchPage.search(normalVideo) + await checkNormalVideo() + + await videoSearchPage.search(nsfwVideo) + await checkNSFWVideo(policy) + } + + async function updateAdminNSFW (nsfw: NSFWPolicy) { + await adminConfigPage.navigateTo('instance-information') + await adminConfigPage.updateNSFWSetting(nsfw) + await adminConfigPage.save() + } + + async function updateUserNSFW (nsfw: NSFWPolicy) { + await myAccountPage.navigateToMySettings() + await myAccountPage.updateNSFW(nsfw) + } + + before(async () => { + await waitServerUp() + }) + + beforeEach(async () => { + videoListPage = new VideoListPage(isMobileDevice(), isSafari()) + adminConfigPage = new AdminConfigPage() + loginPage = new LoginPage() + videoUploadPage = new VideoUploadPage() + myAccountPage = new MyAccountPage() + videoSearchPage = new VideoSearchPage() + + await browser.maximizeWindow() + }) + + it('Should login and disable NSFW', async () => { + await loginPage.loginAsRootUser() + await updateUserNSFW('display') + }) + + it('Should set the homepage', async () => { + await adminConfigPage.navigateTo('instance-homepage') + await adminConfigPage.updateHomepage('') + await adminConfigPage.save() + }) + + it('Should upload 2 videos (NSFW and classic videos)', async () => { + await videoUploadPage.navigateTo() + await videoUploadPage.uploadVideo() + await videoUploadPage.setAsNSFW() + await videoUploadPage.validSecondUploadStep(nsfwVideo) + + await videoUploadPage.navigateTo() + await videoUploadPage.uploadVideo() + await videoUploadPage.validSecondUploadStep(normalVideo) + }) + + it('Should logout', async function () { + await loginPage.logout() + }) + + describe('Anonymous users', function () { + + it('Should correctly handle do not list', async () => { + await loginPage.loginAsRootUser() + await updateAdminNSFW('do_not_list') + + await loginPage.logout() + await checkCommonVideoListPages('do_not_list') + await checkSearchPage('do_not_list') + }) + + it('Should correctly handle blur', async () => { + await loginPage.loginAsRootUser() + await updateAdminNSFW('blur') + + await loginPage.logout() + await checkCommonVideoListPages('blur') + await checkSearchPage('blur') + }) + + it('Should correctly handle display', async () => { + await loginPage.loginAsRootUser() + await updateAdminNSFW('display') + + await loginPage.logout() + await checkCommonVideoListPages('display') + await checkSearchPage('display') + }) + }) + + describe('Logged in users', function () { + + before(async () => { + await loginPage.loginAsRootUser() + }) + + it('Should correctly handle do not list', async () => { + await updateUserNSFW('do_not_list') + await checkCommonVideoListPages('do_not_list') + await checkSearchPage('do_not_list') + }) + + it('Should correctly handle blur', async () => { + await updateUserNSFW('blur') + await checkCommonVideoListPages('blur') + await checkSearchPage('blur') + }) + + it('Should correctly handle display', async () => { + await updateUserNSFW('display') + await checkCommonVideoListPages('display') + await checkSearchPage('display') + }) + }) +}) 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 similarity index 80% rename from client/e2e/src/utils.ts rename to 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) { }) } +async function waitServerUp () { + await browser.waitUntil(async () => { + await go('/') + await browserSleep(500) + + return $('').isDisplayed() + }, { timeout: 20 * 1000 }) +} + export { isMobileDevice, isSafari, isIOS, + waitServerUp, go, browserSleep } 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 @@ +function clickOnCheckbox (name: string) { + return $(`my-peertube-checkbox[inputname=${name}] label`).click() +} + +export { + clickOnCheckbox +} 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 @@ +export * from './common' +export * from './elements' +export * from './urls' diff --git a/client/e2e/src/urls.ts b/client/e2e/src/utils/urls.ts similarity index 100% rename from client/e2e/src/urls.ts rename to 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 @@ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", + "noImplicitAny": false, + "esModuleInterop": true, "module": "commonjs", "target": "es5", "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) { } } -function buildBStackMobileOptions (sessionName: string, deviceName: string, osVersion: string) { +function buildBStackMobileOptions (sessionName: string, deviceName: string, osVersion: string, appiumVersion?: string) { return { 'bstack:options': { ...buildMainOptions(sessionName), realMobile: true, osVersion, - deviceName + deviceName, + + appiumVersion } } } @@ -84,7 +86,7 @@ module.exports = { { browserName: 'Safari', - ...buildBStackMobileOptions('Safari iPhone', 'iPhone 8 Plus', '11') + ...buildBStackMobileOptions('Safari iPhone', 'iPhone SE', '11') }, { browserName: 'Safari', @@ -97,17 +99,20 @@ module.exports = { connectionRetryTimeout: 240000, waitforTimeout: 20000, + specs: [ + // We don't want to test "local" tests + './src/suites-all/*.e2e-spec.ts' + ], + services: [ [ 'browserstack', { browserstackLocal: true } ] ], - after: function (result) { - if (result === 0) { - browser.executeScript('browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"passed","reason": ""}}', []) - } else { - browser.executeScript('browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"failed","reason": ""}}', []) + onWorkerStart: function (_cid, capabilities) { + if (capabilities['bstack:options'].realMobile === true) { + capabilities['bstack:options'].local = false } } } 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 @@ +import { config as mainConfig } from './wdio.main.conf' + +const prefs = { + 'intl.accept_languages': 'en' +} + +module.exports = { + config: { + ...mainConfig, + + runner: 'local', + + maxInstances: 1, + specFileRetries: 0, + + capabilities: [ + { + browserName: 'chrome', + acceptInsecureCerts: true, + 'goog:chromeOptions': { + prefs + } + } + ], + + services: [ 'chromedriver' ] + } as WebdriverIO.Config +} 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 = { runner: 'local', - maxInstances: 1, + maxInstances: 2, capabilities: [ { browserName: 'chrome', - acceptInsecureCerts: true, 'goog:chromeOptions': { prefs } @@ -23,21 +22,20 @@ module.exports = { { browserName: 'firefox', 'moz:firefoxOptions': { - // args: [ '-headless' ], binary: '/usr/bin/firefox-developer-edition', prefs } - }, - { - browserName: 'firefox', - 'moz:firefoxOptions': { - // args: [ '-headless' ], - binary: '/usr/bin/firefox-esr', - prefs - } } ], - services: [ 'chromedriver', 'geckodriver' ] + services: [ 'chromedriver', 'geckodriver' ], + + beforeSession: function (config, capabilities) { + if (capabilities['browserName'] === 'chrome') { + config.baseUrl = 'http://localhost:9001' + } else { + config.baseUrl = 'http://localhost:9002' + } + } } as WebdriverIO.Config } 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 = { // will be called from there. // specs: [ - './src/**/*.e2e-spec.ts' + './src/suites-all/*.e2e-spec.ts', + './src/suites-local/*.e2e-spec.ts' ], // Patterns to exclude. exclude: [ @@ -79,7 +80,7 @@ export const config = { framework: 'mocha', // // The number of times to retry the entire specfile when it fails as a whole - specFileRetries: 2, + specFileRetries: 1, // // Delay in seconds between the spec file retry attempts // specFileRetriesDelay: 0, @@ -105,6 +106,14 @@ export const config = { tsNodeOpts: { project: require('path').join(__dirname, './tsconfig.json') + }, + + tsConfigPathsOpts: { + baseUrl: './', + paths: { + '@server/*': [ '../../server/*' ], + '@shared/*': [ '../../shared/*' ] + } } }, 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 { UserUpdateMe, UserVideoQuota } from '@shared/models' +import { ServerService } from '../' import { environment } from '../../../environments/environment' import { RestExtractor, RestPagination, RestService } from '../rest' import { LocalStorageService, SessionStorageService } from '../wrappers/storage.service' @@ -32,6 +33,7 @@ export class UserService { constructor ( private authHttp: HttpClient, + private server: ServerService, private authService: AuthService, private restExtractor: RestExtractor, private restService: RestService, @@ -298,9 +300,11 @@ export class UserService { console.error('Cannot parse desired video languages from localStorage.', err) } + const defaultNSFWPolicy = this.server.getHTMLConfig().instance.defaultNSFWPolicy + return new User({ // local storage keys - nsfwPolicy: this.localStorageService.getItem(UserLocalStorageKeys.NSFW_POLICY), + nsfwPolicy: this.localStorageService.getItem(UserLocalStorageKeys.NSFW_POLICY) || defaultNSFWPolicy, webTorrentEnabled: this.localStorageService.getItem(UserLocalStorageKeys.WEBTORRENT_ENABLED) !== 'false', theme: this.localStorageService.getItem(UserLocalStorageKeys.THEME) || 'instance-default', 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' import { AuthService } from '@app/core' import { ServerService } from '@app/core/server/server.service' import { UserRight } from '@shared/models' -import { NSFWPolicyType } from '@shared/models/videos' import { PeertubeModalService } from '../shared-main' import { VideoFilters } from './video-filters.model' @@ -18,12 +17,7 @@ const logger = debug('peertube:videos:VideoFiltersHeaderComponent') }) export class VideoFiltersHeaderComponent implements OnInit, OnDestroy { @Input() filters: VideoFilters - @Input() displayModerationBlock = false - - @Input() defaultSort = '-publishedAt' - @Input() nsfwPolicy: NSFWPolicyType - @Input() hideScope = false @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 { } setNSFWPolicy (nsfwPolicy: NSFWPolicyType) { + console.log(nsfwPolicy) + this.updateDefaultNSFW(nsfwPolicy) } 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 @@ 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 npm run concurrently -- -k -s first \ "cd client/e2e && ../node_modules/.bin/wdio run ./wdio.browserstack.conf.ts" \ - "NODE_ENV=test NODE_APP_INSTANCE=1 NODE_CONFIG='{ \"log\": { \"level\": \"warn\" }, \"signup\": { \"enabled\": false } }' node dist/server" + "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 npm run clean:server:test +config="{" +config+=" \"rates_limit\": { \"api\": { \"max\": 5000 }, \"login\": { \"max\": 5000 } }" +config+=", \"log\": { \"level\": \"warn\" }" +config+=", \"signup\": { \"enabled\": false }" +config+=", \"transcoding\": { \"enabled\": false }" +config+="}" + npm run concurrently -- -k -s first \ "cd client/e2e && ../node_modules/.bin/wdio run ./wdio.local.conf.ts" \ - "NODE_ENV=test NODE_APP_INSTANCE=1 NODE_CONFIG='{ \"log\": { \"level\": \"warn\" }, \"signup\": { \"enabled\": false } }' node dist/server" + "NODE_ENV=test NODE_CONFIG='$config' NODE_APP_INSTANCE=1 node dist/server" \ + "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: ``` $ BROWSERSTACK_USER=your_user BROWSERSTACK_KEY=your_key npm run e2e:browserstack ``` + +### Add E2E tests + +To add E2E tests and quickly run tests using a local Chrome, first create a test instance: + +``` +$ npm run clean:server:test && NODE_APP_INSTANCE=1 NODE_ENV=test npm start +``` + +Then, just run your suite using: + +``` +$ cd client/e2e +$ ../node_modules/.bin/wdio wdio.local-test.conf.ts # you can also add --mochaOpts.grep to only run tests you want +```