Init commit
This commit is contained in:
BIN
client/e2e/fixtures/video.mp4
Normal file
BIN
client/e2e/fixtures/video.mp4
Normal file
Binary file not shown.
BIN
client/e2e/fixtures/video2.mp4
Normal file
BIN
client/e2e/fixtures/video2.mp4
Normal file
Binary file not shown.
BIN
client/e2e/fixtures/video3.mp4
Normal file
BIN
client/e2e/fixtures/video3.mp4
Normal file
Binary file not shown.
12
client/e2e/src/commands/upload.ts
Normal file
12
client/e2e/src/commands/upload.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
browser.addCommand('chooseFile', async function (this: WebdriverIO.Element, localFilePath: string) {
|
||||
try {
|
||||
const remoteFile = await browser.uploadFile(localFilePath)
|
||||
|
||||
return this.addValue(remoteFile)
|
||||
} catch {
|
||||
console.log('Cannot upload file, fallback to add value.')
|
||||
|
||||
// Firefox does not support upload file, but if we're running the test in local we don't really need it
|
||||
return this.addValue(localFilePath)
|
||||
}
|
||||
}, true)
|
||||
73
client/e2e/src/po/admin-config.po.ts
Normal file
73
client/e2e/src/po/admin-config.po.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { NSFWPolicyType } from '@peertube/peertube-models'
|
||||
import { browserSleep, go, setCheckboxEnabled } from '../utils'
|
||||
|
||||
export class AdminConfigPage {
|
||||
async navigateTo (page: 'information' | 'live' | 'general' | 'homepage') {
|
||||
const url = '/admin/settings/config/' + page
|
||||
|
||||
const currentUrl = await browser.getUrl()
|
||||
if (!currentUrl.endsWith(url)) {
|
||||
await go(url)
|
||||
}
|
||||
|
||||
await $('a.active[href="' + url + '"]').waitForDisplayed()
|
||||
}
|
||||
|
||||
async updateNSFWSetting (newValue: NSFWPolicyType) {
|
||||
await this.navigateTo('information')
|
||||
|
||||
const elem = $(`#instanceDefaultNSFWPolicy-${newValue} + label`)
|
||||
|
||||
await elem.waitForDisplayed()
|
||||
await elem.scrollIntoView({ block: 'center' }) // Avoid issues with fixed header
|
||||
await elem.waitForClickable()
|
||||
|
||||
return elem.click()
|
||||
}
|
||||
|
||||
async updateHomepage (newValue: string) {
|
||||
await this.navigateTo('homepage')
|
||||
|
||||
return $('#homepageContent').setValue(newValue)
|
||||
}
|
||||
|
||||
async toggleSignup (enabled: boolean) {
|
||||
await this.navigateTo('general')
|
||||
|
||||
return setCheckboxEnabled('signupEnabled', enabled)
|
||||
}
|
||||
|
||||
async toggleSignupApproval (required: boolean) {
|
||||
await this.navigateTo('general')
|
||||
|
||||
return setCheckboxEnabled('signupRequiresApproval', required)
|
||||
}
|
||||
|
||||
async toggleSignupEmailVerification (required: boolean) {
|
||||
await this.navigateTo('general')
|
||||
|
||||
return setCheckboxEnabled('signupRequiresEmailVerification', required)
|
||||
}
|
||||
|
||||
async toggleLive (enabled: boolean) {
|
||||
await this.navigateTo('live')
|
||||
|
||||
return setCheckboxEnabled('liveEnabled', enabled)
|
||||
}
|
||||
|
||||
async save () {
|
||||
const button = $('my-admin-save-bar .save-button')
|
||||
|
||||
try {
|
||||
await button.waitForClickable()
|
||||
} catch {
|
||||
// The config may have not been changed
|
||||
return
|
||||
} finally {
|
||||
await browserSleep(1000) // Wait for the button to be clickable
|
||||
}
|
||||
|
||||
await button.click()
|
||||
await button.waitForClickable({ reverse: true })
|
||||
}
|
||||
}
|
||||
31
client/e2e/src/po/admin-plugin.po.ts
Normal file
31
client/e2e/src/po/admin-plugin.po.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { browserSleep, go } from '../utils'
|
||||
|
||||
export class AdminPluginPage {
|
||||
|
||||
async navigateToPluginSearch () {
|
||||
await go('/admin/settings/plugins/search')
|
||||
|
||||
await $('my-plugin-search').waitForDisplayed()
|
||||
}
|
||||
|
||||
async search (name: string) {
|
||||
const input = $('.search-bar input')
|
||||
await input.waitForDisplayed()
|
||||
await input.clearValue()
|
||||
await input.setValue(name)
|
||||
|
||||
await browserSleep(1000)
|
||||
}
|
||||
|
||||
async installHelloWorld () {
|
||||
$('.plugin-name=hello-world').waitForDisplayed()
|
||||
|
||||
await $('.card-body my-button[icon=cloud-download]').click()
|
||||
|
||||
const submitModalButton = $('.modal-content input[type=submit]')
|
||||
await submitModalButton.waitForClickable()
|
||||
await submitModalButton.click()
|
||||
|
||||
await $('.card-body my-edit-button').waitForDisplayed()
|
||||
}
|
||||
}
|
||||
33
client/e2e/src/po/admin-registration.po.ts
Normal file
33
client/e2e/src/po/admin-registration.po.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { browserSleep, findParentElement, go } from '../utils'
|
||||
|
||||
export class AdminRegistrationPage {
|
||||
async navigateToRegistrationsList () {
|
||||
await go('/admin/moderation/registrations/list')
|
||||
|
||||
await $('my-registration-list').waitForDisplayed()
|
||||
}
|
||||
|
||||
async accept (username: string, moderationResponse: string) {
|
||||
const usernameEl = $('*=' + username)
|
||||
await usernameEl.waitForDisplayed()
|
||||
|
||||
const tr = await findParentElement(usernameEl, async el => await el.getTagName() === 'tr')
|
||||
|
||||
await tr.$('.action-cell .dropdown-root').click()
|
||||
|
||||
const accept = $('span*=Accept this request')
|
||||
await accept.waitForClickable()
|
||||
await accept.click()
|
||||
|
||||
const moderationResponseTextarea = $('#moderationResponse')
|
||||
await moderationResponseTextarea.waitForDisplayed()
|
||||
|
||||
await moderationResponseTextarea.setValue(moderationResponse)
|
||||
|
||||
const submitButton = $('.modal-footer input[type=submit]')
|
||||
await submitButton.waitForClickable()
|
||||
await submitButton.click()
|
||||
|
||||
await browserSleep(1000)
|
||||
}
|
||||
}
|
||||
24
client/e2e/src/po/admin-user.po.ts
Normal file
24
client/e2e/src/po/admin-user.po.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export class AdminUserPage {
|
||||
async createUser (options: {
|
||||
username: string
|
||||
password: string
|
||||
}) {
|
||||
const { username, password } = options
|
||||
|
||||
await $('.menu-link[title=Overview]').click()
|
||||
await $('a*=Create user').click()
|
||||
|
||||
await $('#username').waitForDisplayed()
|
||||
await $('#username').setValue(username)
|
||||
await $('#password').setValue(password)
|
||||
await $('#channelName').setValue(`${username}_channel`)
|
||||
await $('#email').setValue(`${username}@example.com`)
|
||||
|
||||
const submit = $('my-user-create .primary-button')
|
||||
await submit.scrollIntoView()
|
||||
await submit.waitForClickable()
|
||||
await submit.click()
|
||||
|
||||
await $('.cell-username*=' + username).waitForDisplayed()
|
||||
}
|
||||
}
|
||||
45
client/e2e/src/po/anonymous-settings.po.ts
Normal file
45
client/e2e/src/po/anonymous-settings.po.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { NSFWPolicyType } from '@peertube/peertube-models'
|
||||
import { getCheckbox } from '../utils'
|
||||
|
||||
export class AnonymousSettingsPage {
|
||||
async openSettings () {
|
||||
const link = $('my-header .settings-button')
|
||||
await link.waitForClickable()
|
||||
await link.click()
|
||||
|
||||
await $('my-user-video-settings').waitForDisplayed()
|
||||
}
|
||||
|
||||
async closeSettings () {
|
||||
const closeModal = $('.modal.show .modal-header > button')
|
||||
await closeModal.waitForClickable()
|
||||
await closeModal.click()
|
||||
|
||||
await $('.modal.show').waitForDisplayed({ reverse: true })
|
||||
}
|
||||
|
||||
async clickOnP2PCheckbox () {
|
||||
const p2p = await getCheckbox('p2pEnabled')
|
||||
await p2p.waitForClickable()
|
||||
|
||||
await p2p.click()
|
||||
}
|
||||
|
||||
async updateNSFW (newValue: NSFWPolicyType) {
|
||||
const nsfw = $(`#nsfwPolicy-${newValue} + label`)
|
||||
|
||||
await nsfw.waitForClickable()
|
||||
await nsfw.click()
|
||||
|
||||
await $(`#nsfwPolicy-${newValue}:checked`).waitForExist()
|
||||
}
|
||||
|
||||
async updateViolentFlag (newValue: NSFWPolicyType) {
|
||||
const nsfw = $(`#nsfwFlagViolent-${newValue} + label`)
|
||||
|
||||
await nsfw.waitForClickable()
|
||||
await nsfw.click()
|
||||
|
||||
await $(`#nsfwFlagViolent-${newValue}:checked`).waitForExist()
|
||||
}
|
||||
}
|
||||
97
client/e2e/src/po/login.po.ts
Normal file
97
client/e2e/src/po/login.po.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { browserSleep, go, isAndroid } from '../utils'
|
||||
|
||||
export class LoginPage {
|
||||
constructor (private isMobileDevice: boolean) {
|
||||
}
|
||||
|
||||
async login (options: {
|
||||
username: string
|
||||
password: string
|
||||
displayName?: string
|
||||
url?: string
|
||||
}) {
|
||||
const { username, password, url = '/login', displayName = username } = options
|
||||
|
||||
await go(url)
|
||||
|
||||
await browser.execute(`window.localStorage.setItem('no_account_setup_warning_modal', 'true')`)
|
||||
await browser.execute(`window.localStorage.setItem('no_instance_config_warning_modal', 'true')`)
|
||||
await browser.execute(`window.localStorage.setItem('no_welcome_modal', 'true')`)
|
||||
|
||||
await $('input#username').setValue(username)
|
||||
await $('input#password').setValue(password)
|
||||
|
||||
await browserSleep(1000)
|
||||
|
||||
const submit = $('.login-form-and-externals > form input[type=submit]')
|
||||
await submit.click()
|
||||
|
||||
// Have to do this on Android, don't really know why
|
||||
// I think we need to "escape" from the password input, so click twice on the submit button
|
||||
if (isAndroid()) {
|
||||
await browserSleep(2000)
|
||||
await submit.click()
|
||||
}
|
||||
|
||||
await this.ensureIsLoggedInAs(displayName)
|
||||
}
|
||||
|
||||
async getLoginError (username: string, password: string) {
|
||||
await go('/login')
|
||||
|
||||
await $('input#username').setValue(username)
|
||||
await $('input#password').setValue(password)
|
||||
|
||||
await browser.pause(1000)
|
||||
|
||||
await $('form input[type=submit]').click()
|
||||
|
||||
return $('.alert-danger').getText()
|
||||
}
|
||||
|
||||
loginAsRootUser () {
|
||||
return this.login({ username: 'root', password: this.getRootPassword() })
|
||||
}
|
||||
|
||||
getRootPassword () {
|
||||
return 'test' + this.getSuffix()
|
||||
}
|
||||
|
||||
loginOnPeerTube2 () {
|
||||
if (!process.env.PEERTUBE2_E2E_PASSWORD) {
|
||||
throw new Error('PEERTUBE2_E2E_PASSWORD env is missing for user e2e on peertube2.cpy.re')
|
||||
}
|
||||
|
||||
return this.login({ username: 'e2e', password: process.env.PEERTUBE2_E2E_PASSWORD, url: 'https://peertube2.cpy.re/login' })
|
||||
}
|
||||
|
||||
async logout () {
|
||||
const loggedInDropdown = $('.logged-in-container .dropdown-toggle')
|
||||
|
||||
await loggedInDropdown.waitForClickable()
|
||||
await loggedInDropdown.click()
|
||||
|
||||
const logout = $('.dropdown-item.logout')
|
||||
|
||||
await logout.waitForClickable()
|
||||
await logout.click()
|
||||
|
||||
await browser.waitUntil(() => {
|
||||
return $$('my-login-link, my-error-page a[href="/login"]').some(e => e.isDisplayed())
|
||||
})
|
||||
}
|
||||
|
||||
async ensureIsLoggedInAs (displayName: string) {
|
||||
await this.getLoggedInInfoElem(displayName).waitForExist()
|
||||
}
|
||||
|
||||
private getLoggedInInfoElem (displayName: string) {
|
||||
return $('.logged-in-info').$('.display-name*=' + displayName)
|
||||
}
|
||||
|
||||
private getSuffix () {
|
||||
return browser.options.baseUrl
|
||||
? browser.options.baseUrl.slice(-1)
|
||||
: '1'
|
||||
}
|
||||
}
|
||||
203
client/e2e/src/po/my-account.po.ts
Normal file
203
client/e2e/src/po/my-account.po.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import { NSFWPolicyType } from '@peertube/peertube-models'
|
||||
import { getCheckbox, go, selectCustomSelect } from '../utils'
|
||||
|
||||
export class MyAccountPage {
|
||||
navigateToMyVideos () {
|
||||
return $('a[href="/my-library/videos"]').click()
|
||||
}
|
||||
|
||||
navigateToMyPlaylists () {
|
||||
return $('a[href="/my-library/video-playlists"]').click()
|
||||
}
|
||||
|
||||
navigateToMyHistory () {
|
||||
return $('a[href="/my-library/history/videos"]').click()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// My account settings
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
navigateToMySettings () {
|
||||
return $('a[href="/my-account"]').click()
|
||||
}
|
||||
|
||||
async updateNSFW (newValue: NSFWPolicyType) {
|
||||
const nsfw = $(`#nsfwPolicy-${newValue} + label`)
|
||||
|
||||
await nsfw.waitForDisplayed()
|
||||
await nsfw.scrollIntoView({ block: 'center' }) // Avoid issues with fixed header
|
||||
await nsfw.waitForClickable()
|
||||
|
||||
await nsfw.click()
|
||||
|
||||
await this.submitVideoSettings()
|
||||
}
|
||||
|
||||
async updateViolentFlag (newValue: NSFWPolicyType) {
|
||||
const nsfw = $(`#nsfwFlagViolent-${newValue} + label`)
|
||||
|
||||
await nsfw.waitForDisplayed()
|
||||
await nsfw.scrollIntoView({ block: 'center' }) // Avoid issues with fixed header
|
||||
await nsfw.waitForClickable()
|
||||
|
||||
await nsfw.click()
|
||||
|
||||
await this.submitVideoSettings()
|
||||
}
|
||||
|
||||
async clickOnP2PCheckbox () {
|
||||
const p2p = await getCheckbox('p2pEnabled')
|
||||
|
||||
await p2p.waitForClickable()
|
||||
await p2p.scrollIntoView({ block: 'center' }) // Avoid issues with fixed header
|
||||
|
||||
await p2p.click()
|
||||
|
||||
await this.submitVideoSettings()
|
||||
}
|
||||
|
||||
private async submitVideoSettings () {
|
||||
const submit = $('my-user-video-settings input[type=submit]')
|
||||
|
||||
await submit.waitForClickable()
|
||||
await submit.scrollIntoView({ block: 'center' }) // Avoid issues with fixed header
|
||||
await submit.click()
|
||||
}
|
||||
|
||||
async updateEmail (email: string, password: string) {
|
||||
const emailInput = $('my-account-change-email #new-email')
|
||||
await emailInput.waitForDisplayed()
|
||||
await emailInput.scrollIntoView({ block: 'center' }) // Avoid issues with fixed header
|
||||
await emailInput.setValue(email)
|
||||
|
||||
const passwordInput = $('my-account-change-email #password')
|
||||
await passwordInput.waitForDisplayed()
|
||||
await passwordInput.setValue(password)
|
||||
|
||||
const submit = $('my-account-change-email input[type=submit]')
|
||||
await submit.scrollIntoView({ block: 'center' }) // Avoid issues with fixed header
|
||||
await submit.waitForClickable()
|
||||
await submit.click()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// My account videos
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async removeVideo (name: string) {
|
||||
const container = await this.getVideoRow(name)
|
||||
|
||||
await container.$('my-action-dropdown .dropdown-toggle').click()
|
||||
|
||||
const deleteItem = () => {
|
||||
return $$('.dropdown-menu .dropdown-item').find<WebdriverIO.Element>(async v => {
|
||||
const text = await v.getText()
|
||||
|
||||
return text.includes('Delete')
|
||||
})
|
||||
}
|
||||
|
||||
await (await deleteItem()).waitForClickable()
|
||||
|
||||
return (await deleteItem()).click()
|
||||
}
|
||||
|
||||
validRemove () {
|
||||
return $('input[type=submit]').click()
|
||||
}
|
||||
|
||||
async countVideos (names: string[]) {
|
||||
const elements = await $$('.video-cell-name .name').filter(async e => {
|
||||
const t = await e.getText()
|
||||
|
||||
return names.some(n => t.includes(n))
|
||||
})
|
||||
|
||||
return elements.length
|
||||
}
|
||||
|
||||
async getVideoRow (name: string) {
|
||||
let el = $('.name*=' + name)
|
||||
|
||||
await el.waitForDisplayed()
|
||||
|
||||
while (await el.getTagName() !== 'tr') {
|
||||
el = el.parentElement()
|
||||
}
|
||||
|
||||
return el
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// My account playlists
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async getPlaylistVideosText (name: string) {
|
||||
const elem = await this.getPlaylist(name)
|
||||
|
||||
return elem.$('.miniature-playlist-info-overlay').getText()
|
||||
}
|
||||
|
||||
async clickOnPlaylist (name: string) {
|
||||
const elem = await this.getPlaylist(name)
|
||||
|
||||
return elem.$('.miniature-thumbnail').click()
|
||||
}
|
||||
|
||||
async countTotalPlaylistElements () {
|
||||
await $('<my-video-playlist-element-miniature>').waitForDisplayed()
|
||||
|
||||
return $$('<my-video-playlist-element-miniature>').length
|
||||
}
|
||||
|
||||
playPlaylist () {
|
||||
return $('.playlist-info .miniature-thumbnail').click()
|
||||
}
|
||||
|
||||
async goOnAssociatedPlaylistEmbed () {
|
||||
let url = await browser.getUrl()
|
||||
url = url.replace('/w/p/', '/video-playlists/embed/')
|
||||
url = url.replace(':3333', ':9001')
|
||||
|
||||
return go(url)
|
||||
}
|
||||
|
||||
async updatePlaylistPrivacy (playlistUUID: string, privacy: 'Public' | 'Private' | 'Unlisted') {
|
||||
go('/my-library/video-playlists/update/' + playlistUUID)
|
||||
|
||||
await $('a[href*="/my-library/video-playlists/update/"]').waitForDisplayed()
|
||||
|
||||
await selectCustomSelect('videoChannelId', 'Main root channel')
|
||||
await selectCustomSelect('privacy', privacy)
|
||||
|
||||
const submit = $('form input[type=submit]')
|
||||
await submit.waitForClickable()
|
||||
await submit.scrollIntoView()
|
||||
await submit.click()
|
||||
|
||||
return browser.waitUntil(async () => {
|
||||
return (await browser.getUrl()).includes('my-library/video-playlists')
|
||||
})
|
||||
}
|
||||
|
||||
private async getPlaylist (name: string) {
|
||||
const playlist = () => {
|
||||
return $$('my-video-playlist-miniature')
|
||||
.filter(async e => {
|
||||
const t = await e.$('img').getAttribute('aria-label')
|
||||
|
||||
return t.includes(name)
|
||||
})
|
||||
.then(elems => elems[0])
|
||||
}
|
||||
|
||||
await browser.waitUntil(async () => {
|
||||
const el = await playlist()
|
||||
|
||||
return el?.isDisplayed()
|
||||
})
|
||||
|
||||
return playlist()
|
||||
}
|
||||
}
|
||||
107
client/e2e/src/po/player.po.ts
Normal file
107
client/e2e/src/po/player.po.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { browserSleep, isIOS, isMobileDevice, isSafari } from '../utils'
|
||||
|
||||
export class PlayerPage {
|
||||
getWatchVideoPlayerCurrentTime () {
|
||||
const elem = $('video')
|
||||
|
||||
const p = isIOS()
|
||||
? elem.getAttribute('currentTime')
|
||||
: elem.getProperty('currentTime')
|
||||
|
||||
return p.then(t => parseInt(t + '', 10))
|
||||
.then(t => Math.ceil(t))
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
|
||||
waitUntilPlayerWrapper () {
|
||||
return $('#video-wrapper').waitForExist()
|
||||
}
|
||||
|
||||
waitUntilPlaying () {
|
||||
return $('.video-js.vjs-playing').waitForDisplayed()
|
||||
}
|
||||
|
||||
async playAndPauseVideo (isAutoplay: boolean, waitUntilSec: number) {
|
||||
// Autoplay is disabled on mobile and Safari
|
||||
if (isIOS() || isSafari() || isMobileDevice() || isAutoplay === false) {
|
||||
await this.playVideo()
|
||||
}
|
||||
|
||||
await $('div.video-js.vjs-has-started').waitForExist()
|
||||
|
||||
await browserSleep(2000)
|
||||
|
||||
await browser.waitUntil(async () => {
|
||||
return (await this.getWatchVideoPlayerCurrentTime()) >= waitUntilSec
|
||||
}, { timeout: Math.max(waitUntilSec * 2 * 1000, 30000) })
|
||||
|
||||
// Pause video
|
||||
await $('div.video-js').click()
|
||||
}
|
||||
|
||||
async playVideo () {
|
||||
await $('div.video-js.vjs-paused, div.video-js.vjs-playing').waitForExist()
|
||||
|
||||
if (await $('div.video-js.vjs-playing').isExisting()) {
|
||||
if (!isIOS()) return
|
||||
|
||||
// On iOS, the web browser may have aborted player autoplay, so check the video is still autoplayed
|
||||
await browserSleep(5000)
|
||||
if (await $('div.video-js.vjs-playing').isExisting()) return
|
||||
}
|
||||
|
||||
// Autoplay is disabled on iOS and Safari
|
||||
if (isIOS() || isSafari() || isMobileDevice()) {
|
||||
// We can't play the video if it is not muted
|
||||
await browser.execute(`document.querySelector('video').muted = true`)
|
||||
}
|
||||
|
||||
return this.clickOnPlayButton()
|
||||
}
|
||||
|
||||
getPlayButton () {
|
||||
return $('.vjs-big-play-button')
|
||||
}
|
||||
|
||||
getNSFWContentText () {
|
||||
return $('.video-js .nsfw-info').getText()
|
||||
}
|
||||
|
||||
getNSFWDetailsContent () {
|
||||
return $('.video-js .nsfw-details-content')
|
||||
}
|
||||
|
||||
getMoreNSFWInfoButton () {
|
||||
return $('.video-js .nsfw-info button')
|
||||
}
|
||||
|
||||
async hasPoster () {
|
||||
const img = $('.video-js .vjs-poster img')
|
||||
|
||||
return await img.isDisplayed() && (await img.getAttribute('src')).startsWith('http')
|
||||
}
|
||||
|
||||
private async clickOnPlayButton () {
|
||||
await this.getPlayButton().waitForClickable()
|
||||
await this.getPlayButton().click()
|
||||
}
|
||||
|
||||
async fillEmbedVideoPassword (videoPassword: string) {
|
||||
const videoPasswordInput = $('input#video-password-input')
|
||||
const confirmButton = $('button#video-password-submit')
|
||||
|
||||
await videoPasswordInput.clearValue()
|
||||
await videoPasswordInput.setValue(videoPassword)
|
||||
await confirmButton.waitForClickable()
|
||||
|
||||
return confirmButton.click()
|
||||
}
|
||||
}
|
||||
87
client/e2e/src/po/signup.po.ts
Normal file
87
client/e2e/src/po/signup.po.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { getCheckbox } from '../utils'
|
||||
|
||||
export class SignupPage {
|
||||
getRegisterMenuButton () {
|
||||
return $('.create-account-button')
|
||||
}
|
||||
|
||||
async clickOnRegisterButton () {
|
||||
const button = this.getRegisterMenuButton()
|
||||
|
||||
await button.waitForClickable()
|
||||
await button.click()
|
||||
}
|
||||
|
||||
async validateStep () {
|
||||
const next = $('button[type=submit]')
|
||||
|
||||
await next.scrollIntoView({ block: 'center' })
|
||||
await next.waitForClickable()
|
||||
await next.click()
|
||||
}
|
||||
|
||||
async checkTerms () {
|
||||
const terms = await getCheckbox('terms')
|
||||
await terms.waitForClickable()
|
||||
|
||||
return terms.click()
|
||||
}
|
||||
|
||||
async getEndMessage () {
|
||||
const alert = $('.pt-alert-primary')
|
||||
await alert.waitForDisplayed()
|
||||
|
||||
return alert.getText()
|
||||
}
|
||||
|
||||
async fillRegistrationReason (reason: string) {
|
||||
await $('#registrationReason').setValue(reason)
|
||||
}
|
||||
|
||||
async fillAccountStep (options: {
|
||||
username: string
|
||||
password?: string
|
||||
displayName?: string
|
||||
email?: string
|
||||
}) {
|
||||
await $('#displayName').setValue(options.displayName || `${options.username} display name`)
|
||||
|
||||
await $('#username').setValue(options.username)
|
||||
await $('#password').setValue(options.password || 'superpassword')
|
||||
|
||||
// Fix weird bug on firefox that "cannot scroll into view" when using just `setValue`
|
||||
await $('#email').scrollIntoView({ block: 'center' })
|
||||
await $('#email').waitForClickable()
|
||||
await $('#email').setValue(options.email || `${options.username}@example.com`)
|
||||
}
|
||||
|
||||
async fillChannelStep (options: {
|
||||
name: string
|
||||
displayName?: string
|
||||
}) {
|
||||
await $('#displayName').setValue(options.displayName || `${options.name} channel display name`)
|
||||
await $('#name').setValue(options.name)
|
||||
}
|
||||
|
||||
async fullSignup ({ accountInfo, channelInfo }: {
|
||||
accountInfo: {
|
||||
username: string
|
||||
password?: string
|
||||
displayName?: string
|
||||
email?: string
|
||||
}
|
||||
channelInfo: {
|
||||
name: string
|
||||
}
|
||||
}) {
|
||||
await this.clickOnRegisterButton()
|
||||
await this.validateStep()
|
||||
await this.checkTerms()
|
||||
await this.validateStep()
|
||||
await this.fillAccountStep(accountInfo)
|
||||
await this.validateStep()
|
||||
await this.fillChannelStep(channelInfo)
|
||||
await this.validateStep()
|
||||
await this.getEndMessage()
|
||||
}
|
||||
}
|
||||
140
client/e2e/src/po/video-list.po.ts
Normal file
140
client/e2e/src/po/video-list.po.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { browserSleep, findParentElement, 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/browse?scope=local'
|
||||
} else {
|
||||
url = '/videos/browse'
|
||||
}
|
||||
|
||||
await go(url)
|
||||
|
||||
// Waiting the following element does not work on Safari...
|
||||
if (this.isSafari) return browserSleep(3000)
|
||||
|
||||
await this.waitForList()
|
||||
}
|
||||
|
||||
async goOnBrowseVideos () {
|
||||
await $('.menu-link*=Home').click()
|
||||
|
||||
const browseVideos = $('a*=Browse videos')
|
||||
await browseVideos.waitForClickable()
|
||||
await browseVideos.click()
|
||||
await this.waitForList()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
async getNSFWFilterText () {
|
||||
const el = $('.active-filter*=Sensitive')
|
||||
|
||||
await el.waitForDisplayed()
|
||||
|
||||
return el.getText()
|
||||
}
|
||||
|
||||
async getVideosListName () {
|
||||
const elems = $$('.videos .video-miniature .video-name')
|
||||
const texts = await elems.map(e => e.getText())
|
||||
|
||||
return texts.map(t => t.trim())
|
||||
}
|
||||
|
||||
isVideoDisplayed (name: string) {
|
||||
return $('.video-name=' + name).isDisplayed()
|
||||
}
|
||||
|
||||
async isVideoBlurred (name: string) {
|
||||
const miniature = await this.getVideoMiniature(name)
|
||||
const filter = await miniature.$('my-video-thumbnail img').getCSSProperty('filter')
|
||||
|
||||
return filter.value !== 'none'
|
||||
}
|
||||
|
||||
async hasVideoWarning (name: string) {
|
||||
const miniature = await this.getVideoMiniature(name)
|
||||
|
||||
return miniature.$('.nsfw-warning').isDisplayed()
|
||||
}
|
||||
|
||||
async expectVideoNSFWTooltip (name: string, summary?: string) {
|
||||
const miniature = await this.getVideoMiniature(name)
|
||||
|
||||
const warning = miniature.$('.nsfw-warning')
|
||||
await warning.waitForDisplayed()
|
||||
|
||||
expect(await warning.getAttribute('aria-label')).toEqual(summary)
|
||||
}
|
||||
|
||||
private async getVideoMiniature (name: string) {
|
||||
const videoName = $('.video-name=' + name)
|
||||
await videoName.waitForDisplayed()
|
||||
|
||||
return findParentElement(videoName, async el => await el.getTagName() === 'my-video-miniature')
|
||||
}
|
||||
|
||||
async clickOnVideo (videoName: string) {
|
||||
const video = async () => {
|
||||
const videos = await $$('.videos .video-miniature .video-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-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-name').waitForDisplayed()
|
||||
}
|
||||
}
|
||||
148
client/e2e/src/po/video-manage.ts
Normal file
148
client/e2e/src/po/video-manage.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import { clickOnRadio, getCheckbox, go, isRadioSelected, selectCustomSelect, setCheckboxEnabled } from '../utils'
|
||||
|
||||
export abstract class VideoManage {
|
||||
async clickOnSave () {
|
||||
const button = this.getSaveButton()
|
||||
await button.waitForClickable()
|
||||
await button.click()
|
||||
|
||||
await this.waitForSaved()
|
||||
}
|
||||
|
||||
async clickOnWatch () {
|
||||
// Simulate the click, because the button opens a new tab
|
||||
const button = $('.watch-save > my-button[icon=external-link] a')
|
||||
await button.waitForClickable()
|
||||
|
||||
await go(await button.getAttribute('href'))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async setAsNSFW (options: {
|
||||
violent?: boolean
|
||||
summary?: string
|
||||
} = {}) {
|
||||
await this.goOnPage('Moderation')
|
||||
|
||||
const checkbox = await getCheckbox('nsfw')
|
||||
await checkbox.waitForClickable()
|
||||
|
||||
await checkbox.click()
|
||||
|
||||
if (options.violent) {
|
||||
await setCheckboxEnabled('nsfwFlagViolent', true)
|
||||
}
|
||||
|
||||
if (options.summary) {
|
||||
await $('#nsfwSummary').setValue(options.summary)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async setAsPublic () {
|
||||
await this.goOnPage('Main information')
|
||||
|
||||
return selectCustomSelect('privacy', 'Public')
|
||||
}
|
||||
|
||||
async setAsPrivate () {
|
||||
await this.goOnPage('Main information')
|
||||
|
||||
return selectCustomSelect('privacy', 'Private')
|
||||
}
|
||||
|
||||
async setAsPasswordProtected (videoPassword: string) {
|
||||
await this.goOnPage('Main information')
|
||||
|
||||
selectCustomSelect('privacy', 'Password protected')
|
||||
|
||||
const videoPasswordInput = $('input#videoPassword')
|
||||
await videoPasswordInput.waitForClickable()
|
||||
await videoPasswordInput.clearValue()
|
||||
|
||||
return videoPasswordInput.setValue(videoPassword)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async scheduleUpload () {
|
||||
await this.goOnPage('Main information')
|
||||
|
||||
selectCustomSelect('privacy', 'Scheduled')
|
||||
|
||||
const input = this.getScheduleInput()
|
||||
await input.waitForClickable()
|
||||
await input.click()
|
||||
|
||||
const nextMonth = $('.p-datepicker-next-button')
|
||||
await nextMonth.click()
|
||||
|
||||
await $('.p-datepicker-calendar td[aria-label="1"] > span').click()
|
||||
await $('.p-datepicker-calendar').waitForDisplayed({ reverse: true, timeout: 15000 }) // Can be slow
|
||||
}
|
||||
|
||||
getScheduleInput () {
|
||||
return $('#schedulePublicationAt input')
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async setNormalLive () {
|
||||
await this.goOnPage('Live settings')
|
||||
|
||||
await clickOnRadio('permanentLiveFalse')
|
||||
}
|
||||
|
||||
async setPermanentLive () {
|
||||
await this.goOnPage('Live settings')
|
||||
|
||||
await clickOnRadio('permanentLiveTrue')
|
||||
}
|
||||
|
||||
async getLiveState () {
|
||||
await this.goOnPage('Live settings')
|
||||
|
||||
if (await isRadioSelected('permanentLiveTrue')) return 'permanent'
|
||||
|
||||
return 'normal'
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async refresh (videoName: string) {
|
||||
await browser.refresh()
|
||||
await browser.waitUntil(async () => {
|
||||
const url = await browser.getUrl()
|
||||
|
||||
return url.includes('/videos/manage')
|
||||
})
|
||||
|
||||
await browser.waitUntil(async () => {
|
||||
return await $('#name').getValue() === videoName
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
protected getSaveButton () {
|
||||
return $('.save-button > button:not([disabled])')
|
||||
}
|
||||
|
||||
protected waitForSaved () {
|
||||
return $('.save-button > button[disabled], my-manage-errors').waitForDisplayed()
|
||||
}
|
||||
|
||||
protected async goOnPage (page: 'Main information' | 'Moderation' | 'Live settings') {
|
||||
const urls = {
|
||||
'Main information': '',
|
||||
'Moderation': 'moderation',
|
||||
'Live settings': 'live'
|
||||
}
|
||||
|
||||
const el = $(`my-video-manage-container .menu a[href*="/${urls[page]}"]`)
|
||||
await el.waitForClickable()
|
||||
await el.click()
|
||||
}
|
||||
}
|
||||
80
client/e2e/src/po/video-publish.po.ts
Normal file
80
client/e2e/src/po/video-publish.po.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { join } from 'node:path'
|
||||
import { VideoManage } from './video-manage'
|
||||
import { FIXTURE_URLS } from '../utils'
|
||||
|
||||
export class VideoPublishPage extends VideoManage {
|
||||
async navigateTo (tab?: 'Go live') {
|
||||
const publishButton = $('.publish-button > a')
|
||||
|
||||
await publishButton.waitForClickable()
|
||||
await publishButton.click()
|
||||
|
||||
await $('.upload-video-container').waitForDisplayed()
|
||||
|
||||
if (tab) {
|
||||
const el = $(`.nav-link*=${tab}`)
|
||||
await el.waitForClickable()
|
||||
await el.click()
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async uploadVideo (fixtureName: 'video.mp4' | 'video2.mp4' | 'video3.mp4') {
|
||||
const fileToUpload = join(__dirname, '../../fixtures/' + fixtureName)
|
||||
const fileInputSelector = '.upload-video-container input[type=file]'
|
||||
const parentFileInput = '.upload-video-container .button-file'
|
||||
|
||||
// Avoid sending keys on non visible element
|
||||
await browser.execute(`document.querySelector('${fileInputSelector}').style.opacity = 1`)
|
||||
await browser.execute(`document.querySelector('${parentFileInput}').style.overflow = 'initial'`)
|
||||
|
||||
await browser.pause(1000)
|
||||
|
||||
const elem = $(fileInputSelector)
|
||||
await elem.chooseFile(fileToUpload)
|
||||
|
||||
// Wait for the upload to finish
|
||||
await this.getSaveButton().waitForClickable()
|
||||
}
|
||||
|
||||
async importVideo () {
|
||||
const tab = $('.nav-link*=Import with URL')
|
||||
await tab.waitForClickable()
|
||||
await tab.click()
|
||||
|
||||
const input = $('#targetUrl')
|
||||
await input.waitForDisplayed()
|
||||
await input.setValue(FIXTURE_URLS.IMPORT_URL)
|
||||
|
||||
const submit = $('.first-step-block .primary-button:not([disabled])')
|
||||
await submit.waitForClickable()
|
||||
await submit.click()
|
||||
|
||||
// Wait for the import to finish
|
||||
await this.getSaveButton().waitForClickable({ timeout: 15000 }) // Can be slow
|
||||
}
|
||||
|
||||
async publishLive () {
|
||||
await $('#permanentLiveTrue').parentElement().click()
|
||||
|
||||
const submit = $('.upload-video-container .primary-button:not([disabled])')
|
||||
await submit.waitForClickable()
|
||||
await submit.click()
|
||||
|
||||
await this.getSaveButton().waitForClickable()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async validSecondStep (videoName: string) {
|
||||
await this.goOnPage('Main information')
|
||||
|
||||
const nameInput = $('input#name')
|
||||
await nameInput.scrollIntoView()
|
||||
await nameInput.clearValue()
|
||||
await nameInput.setValue(videoName)
|
||||
|
||||
await this.clickOnSave()
|
||||
}
|
||||
}
|
||||
11
client/e2e/src/po/video-search.po.ts
Normal file
11
client/e2e/src/po/video-search.po.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export class VideoSearchPage {
|
||||
|
||||
async search (search: string) {
|
||||
await $('#search-video').setValue(search)
|
||||
await $('.search-button').click()
|
||||
|
||||
await browser.waitUntil(() => {
|
||||
return $('my-video-miniature').isDisplayed()
|
||||
})
|
||||
}
|
||||
}
|
||||
11
client/e2e/src/po/video-update.po.ts
Normal file
11
client/e2e/src/po/video-update.po.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { VideoManage } from './video-manage'
|
||||
|
||||
export class VideoUpdatePage extends VideoManage {
|
||||
async updateName (videoName: string) {
|
||||
const nameInput = $('input#name')
|
||||
|
||||
await nameInput.waitForDisplayed()
|
||||
await nameInput.clearValue()
|
||||
await nameInput.setValue(videoName)
|
||||
}
|
||||
}
|
||||
229
client/e2e/src/po/video-watch.po.ts
Normal file
229
client/e2e/src/po/video-watch.po.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
import { browserSleep, FIXTURE_URLS, go } from '../utils'
|
||||
|
||||
export class VideoWatchPage {
|
||||
constructor (private isMobileDevice: boolean, private isSafari: boolean) {
|
||||
}
|
||||
|
||||
waitWatchVideoName (videoName: string, maxTime?: number) {
|
||||
if (this.isSafari) return browserSleep(5000)
|
||||
|
||||
// On mobile we display the first node, on desktop the second one
|
||||
return browser.waitUntil(async () => {
|
||||
return (await this.getVideoName()) === videoName
|
||||
}, { timeout: maxTime })
|
||||
}
|
||||
|
||||
getVideoName () {
|
||||
return this.getVideoNameElement().then(e => e.getText())
|
||||
}
|
||||
|
||||
getPrivacy () {
|
||||
return $('.attribute-privacy .attribute-value').getText()
|
||||
}
|
||||
|
||||
getLicence () {
|
||||
return $('.attribute-licence .attribute-value').getText()
|
||||
}
|
||||
|
||||
async isDownloadEnabled () {
|
||||
try {
|
||||
await this.clickOnMoreDropdownIcon()
|
||||
|
||||
return await $('.dropdown-item .icon-download').isExisting()
|
||||
} catch {
|
||||
return $('.action-button-download').isDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
areCommentsEnabled () {
|
||||
return $('my-video-comment-add').isExisting()
|
||||
}
|
||||
|
||||
isPrivacyWarningDisplayed () {
|
||||
return $('.privacy-concerns-text').isDisplayed()
|
||||
}
|
||||
|
||||
async goOnAssociatedEmbed (passwordProtected = false) {
|
||||
let url = await browser.getUrl()
|
||||
url = url.replace('/w/', '/videos/embed/')
|
||||
url = url.replace(':3333', ':9001')
|
||||
|
||||
await go(url)
|
||||
|
||||
if (passwordProtected) await this.waitEmbedForVideoPasswordForm()
|
||||
else await this.waitEmbedForDisplayed()
|
||||
}
|
||||
|
||||
waitEmbedForDisplayed () {
|
||||
return $('.vjs-big-play-button').waitForDisplayed()
|
||||
}
|
||||
|
||||
waitEmbedForVideoPasswordForm () {
|
||||
return $('#video-password-input').waitForDisplayed()
|
||||
}
|
||||
|
||||
isEmbedWarningDisplayed () {
|
||||
return $('.peertube-dock-description').isDisplayed()
|
||||
}
|
||||
|
||||
goOnP2PMediaLoaderEmbed () {
|
||||
return go(FIXTURE_URLS.HLS_EMBED)
|
||||
}
|
||||
|
||||
goOnP2PMediaLoaderPlaylistEmbed () {
|
||||
return go(FIXTURE_URLS.HLS_PLAYLIST_EMBED)
|
||||
}
|
||||
|
||||
getModalTitleEl () {
|
||||
return $('.modal-content .modal-title')
|
||||
}
|
||||
|
||||
confirmModal () {
|
||||
return $('.modal-content .modal-footer .primary-button').click()
|
||||
}
|
||||
|
||||
private getVideoNameElement () {
|
||||
return $('.video-info-first-row .video-info-name')
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Video password
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async fillVideoPassword (videoPassword: string) {
|
||||
const videoPasswordInput = $('input#confirmInput')
|
||||
await videoPasswordInput.waitForClickable()
|
||||
await videoPasswordInput.clearValue()
|
||||
await videoPasswordInput.setValue(videoPassword)
|
||||
|
||||
const confirmButton = $('input[value="Confirm"]')
|
||||
await confirmButton.waitForClickable()
|
||||
return confirmButton.click()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Video actions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async like () {
|
||||
const likeButton = $('.action-button-like')
|
||||
const isActivated = (await likeButton.getAttribute('class')).includes('activated')
|
||||
|
||||
let count: number
|
||||
try {
|
||||
count = parseInt(await $('.action-button-like > .count').getText())
|
||||
} catch (error) {
|
||||
count = 0
|
||||
}
|
||||
|
||||
await likeButton.waitForClickable()
|
||||
await likeButton.click()
|
||||
|
||||
if (isActivated) {
|
||||
if (count === 1) {
|
||||
return expect(!await $('.action-button-like > .count').isExisting())
|
||||
} else {
|
||||
return expect(parseInt(await $('.action-button-like > .count').getText())).toBe(count - 1)
|
||||
}
|
||||
} else {
|
||||
return expect(parseInt(await $('.action-button-like > .count').getText())).toBe(count + 1)
|
||||
}
|
||||
}
|
||||
|
||||
async clickOnManage () {
|
||||
await this.clickOnMoreDropdownIcon()
|
||||
|
||||
// We need the await expression
|
||||
return $$('.dropdown-menu.show .dropdown-item').forEach(async item => {
|
||||
const content = await item.getText()
|
||||
|
||||
if (content.includes('Manage')) {
|
||||
await item.click()
|
||||
await $('#name').waitForClickable()
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async clickOnMoreDropdownIcon () {
|
||||
const dropdown = $('my-video-actions-dropdown .action-button')
|
||||
await dropdown.scrollIntoView({ block: 'center' })
|
||||
await dropdown.click()
|
||||
|
||||
await $('.dropdown-menu.show .dropdown-item').waitForDisplayed()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Playlists
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async clickOnSave () {
|
||||
const button = $('.action-button-save')
|
||||
|
||||
await button.scrollIntoView({ block: 'center' })
|
||||
|
||||
return button.click()
|
||||
}
|
||||
|
||||
async createPlaylist (name: string) {
|
||||
const newPlaylistButton = () => $('.new-playlist-button')
|
||||
|
||||
await newPlaylistButton().waitForClickable()
|
||||
await newPlaylistButton().click()
|
||||
|
||||
const displayName = () => $('#displayName')
|
||||
|
||||
await displayName().waitForDisplayed()
|
||||
await displayName().setValue(name)
|
||||
|
||||
return $('.new-playlist-block input[type=submit]').click()
|
||||
}
|
||||
|
||||
async saveToPlaylist (name: string) {
|
||||
const playlist = () => $('my-video-add-to-playlist').$(`.playlist=${name}`)
|
||||
|
||||
await playlist().waitForDisplayed()
|
||||
|
||||
return playlist().click()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Comments
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async createThread (comment: string) {
|
||||
const textarea = $('my-video-comment-add textarea')
|
||||
await textarea.waitForClickable()
|
||||
|
||||
await textarea.setValue(comment)
|
||||
|
||||
const confirmButton = $('.comment-buttons .primary-button')
|
||||
await confirmButton.waitForClickable()
|
||||
await confirmButton.click()
|
||||
|
||||
const createdComment = await $('.comment-html p').getText()
|
||||
|
||||
return expect(createdComment).toBe(comment)
|
||||
}
|
||||
|
||||
async createReply (comment: string) {
|
||||
const replyButton = $('button.comment-action-reply')
|
||||
await replyButton.waitForClickable()
|
||||
await replyButton.scrollIntoView({ block: 'center' })
|
||||
await replyButton.click()
|
||||
|
||||
const textarea = $('my-video-comment my-video-comment-add textarea')
|
||||
await textarea.waitForClickable()
|
||||
await textarea.setValue(comment)
|
||||
|
||||
const confirmButton = $('my-video-comment .comment-buttons .primary-button')
|
||||
await confirmButton.waitForClickable()
|
||||
await replyButton.scrollIntoView({ block: 'center' })
|
||||
await confirmButton.click()
|
||||
|
||||
const createdComment = $('.is-child .comment-html p')
|
||||
await createdComment.waitForDisplayed()
|
||||
|
||||
return expect(await createdComment.getText()).toBe(comment)
|
||||
}
|
||||
}
|
||||
33
client/e2e/src/suites-all/live.e2e-spec.ts
Normal file
33
client/e2e/src/suites-all/live.e2e-spec.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { PlayerPage } from '../po/player.po'
|
||||
import { VideoWatchPage } from '../po/video-watch.po'
|
||||
import { FIXTURE_URLS, go, isMobileDevice, isSafari, prepareWebBrowser } from '../utils'
|
||||
|
||||
describe('Live all workflow', () => {
|
||||
let videoWatchPage: VideoWatchPage
|
||||
let playerPage: PlayerPage
|
||||
|
||||
beforeEach(async () => {
|
||||
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
|
||||
playerPage = new PlayerPage()
|
||||
|
||||
await prepareWebBrowser()
|
||||
})
|
||||
|
||||
it('Should go to the live page', async () => {
|
||||
await go(FIXTURE_URLS.LIVE_VIDEO)
|
||||
|
||||
return videoWatchPage.waitWatchVideoName('E2E - Live')
|
||||
})
|
||||
|
||||
it('Should play the live', async () => {
|
||||
await playerPage.playAndPauseVideo(false, 45)
|
||||
expect(await playerPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(45)
|
||||
})
|
||||
|
||||
it('Should watch the associated live embed', async () => {
|
||||
await videoWatchPage.goOnAssociatedEmbed()
|
||||
|
||||
await playerPage.playAndPauseVideo(false, 45)
|
||||
expect(await playerPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(45)
|
||||
})
|
||||
})
|
||||
73
client/e2e/src/suites-all/private-videos.e2e-spec.ts
Normal file
73
client/e2e/src/suites-all/private-videos.e2e-spec.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { LoginPage } from '../po/login.po'
|
||||
import { PlayerPage } from '../po/player.po'
|
||||
import { VideoWatchPage } from '../po/video-watch.po'
|
||||
import { FIXTURE_URLS, go, isMobileDevice, isSafari, prepareWebBrowser } from '../utils'
|
||||
|
||||
async function checkCorrectlyPlay (playerPage: PlayerPage) {
|
||||
await playerPage.playAndPauseVideo(false, 2)
|
||||
|
||||
expect(await playerPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(2)
|
||||
}
|
||||
|
||||
describe('Private videos all workflow', () => {
|
||||
let videoWatchPage: VideoWatchPage
|
||||
let loginPage: LoginPage
|
||||
let playerPage: PlayerPage
|
||||
|
||||
const internalVideoName = 'Internal E2E test'
|
||||
const internalHLSOnlyVideoName = 'Internal E2E test - HLS only'
|
||||
|
||||
beforeEach(async () => {
|
||||
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
|
||||
loginPage = new LoginPage(isMobileDevice())
|
||||
playerPage = new PlayerPage()
|
||||
|
||||
await prepareWebBrowser()
|
||||
})
|
||||
|
||||
it('Should log in', async () => {
|
||||
return loginPage.loginOnPeerTube2()
|
||||
})
|
||||
|
||||
it('Should play an internal web video', async () => {
|
||||
await go(FIXTURE_URLS.INTERNAL_WEB_VIDEO)
|
||||
|
||||
await videoWatchPage.waitWatchVideoName(internalVideoName)
|
||||
await checkCorrectlyPlay(playerPage)
|
||||
})
|
||||
|
||||
it('Should play an internal HLS video', async () => {
|
||||
await go(FIXTURE_URLS.INTERNAL_HLS_VIDEO)
|
||||
|
||||
await videoWatchPage.waitWatchVideoName(internalVideoName)
|
||||
await checkCorrectlyPlay(playerPage)
|
||||
})
|
||||
|
||||
it('Should play an internal HLS only video', async () => {
|
||||
await go(FIXTURE_URLS.INTERNAL_HLS_ONLY_VIDEO)
|
||||
|
||||
await videoWatchPage.waitWatchVideoName(internalHLSOnlyVideoName)
|
||||
await checkCorrectlyPlay(playerPage)
|
||||
})
|
||||
|
||||
it('Should play an internal Web Video in embed', async () => {
|
||||
await go(FIXTURE_URLS.INTERNAL_EMBED_WEB_VIDEO)
|
||||
|
||||
await videoWatchPage.waitEmbedForDisplayed()
|
||||
await checkCorrectlyPlay(playerPage)
|
||||
})
|
||||
|
||||
it('Should play an internal HLS video in embed', async () => {
|
||||
await go(FIXTURE_URLS.INTERNAL_EMBED_HLS_VIDEO)
|
||||
|
||||
await videoWatchPage.waitEmbedForDisplayed()
|
||||
await checkCorrectlyPlay(playerPage)
|
||||
})
|
||||
|
||||
it('Should play an internal HLS only video in embed', async () => {
|
||||
await go(FIXTURE_URLS.INTERNAL_EMBED_HLS_ONLY_VIDEO)
|
||||
|
||||
await videoWatchPage.waitEmbedForDisplayed()
|
||||
await checkCorrectlyPlay(playerPage)
|
||||
})
|
||||
})
|
||||
233
client/e2e/src/suites-all/videos.e2e-spec.ts
Normal file
233
client/e2e/src/suites-all/videos.e2e-spec.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
import { LoginPage } from '../po/login.po'
|
||||
import { MyAccountPage } from '../po/my-account.po'
|
||||
import { PlayerPage } from '../po/player.po'
|
||||
import { VideoListPage } from '../po/video-list.po'
|
||||
import { VideoPublishPage } from '../po/video-publish.po'
|
||||
import { VideoUpdatePage } from '../po/video-update.po'
|
||||
import { VideoWatchPage } from '../po/video-watch.po'
|
||||
import { FIXTURE_URLS, go, isIOS, isMobileDevice, isSafari, prepareWebBrowser, waitServerUp } from '../utils'
|
||||
|
||||
function isUploadUnsupported () {
|
||||
if (isMobileDevice() || isSafari()) {
|
||||
console.log('Skipping because we are on a real device or Safari and BrowserStack does not support file upload.')
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
describe('Videos all workflow', () => {
|
||||
let videoWatchPage: VideoWatchPage
|
||||
let videoListPage: VideoListPage
|
||||
let videoPublishPage: VideoPublishPage
|
||||
let videoUpdatePage: VideoUpdatePage
|
||||
let myAccountPage: MyAccountPage
|
||||
let loginPage: LoginPage
|
||||
let playerPage: PlayerPage
|
||||
|
||||
let videoName = Math.random() + ' video'
|
||||
const video2Name = Math.random() + ' second video'
|
||||
const playlistName = Math.random() + ' playlist'
|
||||
let videoWatchUrl: string
|
||||
|
||||
before(async () => {
|
||||
if (isIOS()) {
|
||||
console.log('iOS detected')
|
||||
} else if (isMobileDevice()) {
|
||||
console.log('Android detected.')
|
||||
} else if (isSafari()) {
|
||||
console.log('Safari detected.')
|
||||
}
|
||||
|
||||
if (isUploadUnsupported()) return
|
||||
|
||||
await waitServerUp()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
|
||||
videoPublishPage = new VideoPublishPage()
|
||||
videoUpdatePage = new VideoUpdatePage()
|
||||
myAccountPage = new MyAccountPage()
|
||||
loginPage = new LoginPage(isMobileDevice())
|
||||
playerPage = new PlayerPage()
|
||||
videoListPage = new VideoListPage(isMobileDevice(), isSafari())
|
||||
|
||||
await prepareWebBrowser()
|
||||
})
|
||||
|
||||
it('Should log in', async () => {
|
||||
if (isMobileDevice() || isSafari()) {
|
||||
console.log('Skipping because we are on a real device or Safari and BrowserStack does not support file upload.')
|
||||
return
|
||||
}
|
||||
|
||||
return loginPage.loginAsRootUser()
|
||||
})
|
||||
|
||||
it('Should upload a video', async () => {
|
||||
if (isUploadUnsupported()) return
|
||||
|
||||
await videoPublishPage.navigateTo()
|
||||
|
||||
await videoPublishPage.uploadVideo('video.mp4')
|
||||
await videoPublishPage.validSecondStep(videoName)
|
||||
})
|
||||
|
||||
it('Should list videos', async () => {
|
||||
await videoListPage.goOnVideosList()
|
||||
|
||||
if (isUploadUnsupported()) return
|
||||
|
||||
const videoNames = await videoListPage.getVideosListName()
|
||||
expect(videoNames).toContain(videoName)
|
||||
})
|
||||
|
||||
it('Should go on video watch page', async () => {
|
||||
let videoNameToExcept = videoName
|
||||
|
||||
if (isMobileDevice() || isSafari()) {
|
||||
await go(FIXTURE_URLS.WEB_VIDEO)
|
||||
videoNameToExcept = 'E2E tests'
|
||||
} else {
|
||||
await videoListPage.clickOnVideo(videoName)
|
||||
}
|
||||
|
||||
return videoWatchPage.waitWatchVideoName(videoNameToExcept)
|
||||
})
|
||||
|
||||
it('Should play the video', async () => {
|
||||
videoWatchUrl = await browser.getUrl()
|
||||
|
||||
await playerPage.playAndPauseVideo(true, 2)
|
||||
expect(await playerPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(2)
|
||||
})
|
||||
|
||||
it('Should watch the associated embed video', async () => {
|
||||
await videoWatchPage.goOnAssociatedEmbed()
|
||||
|
||||
await playerPage.playAndPauseVideo(false, 2)
|
||||
expect(await playerPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(2)
|
||||
})
|
||||
|
||||
it('Should watch the p2p media loader embed video', async () => {
|
||||
await videoWatchPage.goOnP2PMediaLoaderEmbed()
|
||||
|
||||
await playerPage.playAndPauseVideo(false, 2)
|
||||
expect(await playerPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(2)
|
||||
})
|
||||
|
||||
it('Should update the video', async () => {
|
||||
if (isUploadUnsupported()) return
|
||||
|
||||
await go(videoWatchUrl)
|
||||
|
||||
await videoWatchPage.clickOnManage()
|
||||
|
||||
videoName += ' updated'
|
||||
await videoUpdatePage.updateName(videoName)
|
||||
await videoUpdatePage.clickOnSave()
|
||||
await videoUpdatePage.clickOnWatch()
|
||||
|
||||
const name = await videoWatchPage.getVideoName()
|
||||
expect(name).toEqual(videoName)
|
||||
})
|
||||
|
||||
it('Should add the video in my playlist', async () => {
|
||||
if (isUploadUnsupported()) return
|
||||
|
||||
await videoWatchPage.clickOnSave()
|
||||
|
||||
await videoWatchPage.createPlaylist(playlistName)
|
||||
|
||||
await videoWatchPage.saveToPlaylist(playlistName)
|
||||
await browser.pause(5000)
|
||||
|
||||
await videoPublishPage.navigateTo()
|
||||
|
||||
await videoPublishPage.uploadVideo('video2.mp4')
|
||||
await videoPublishPage.validSecondStep(video2Name)
|
||||
await videoPublishPage.clickOnWatch()
|
||||
|
||||
await videoWatchPage.clickOnSave()
|
||||
await videoWatchPage.saveToPlaylist(playlistName)
|
||||
})
|
||||
|
||||
it('Should have the playlist in my account', async () => {
|
||||
if (isUploadUnsupported()) return
|
||||
|
||||
await myAccountPage.navigateToMyPlaylists()
|
||||
|
||||
const videosNumberText = await myAccountPage.getPlaylistVideosText(playlistName)
|
||||
expect(videosNumberText).toEqual('2 videos')
|
||||
|
||||
await myAccountPage.clickOnPlaylist(playlistName)
|
||||
|
||||
const count = await myAccountPage.countTotalPlaylistElements()
|
||||
expect(count).toEqual(2)
|
||||
})
|
||||
|
||||
it('Should watch the playlist', async () => {
|
||||
if (isUploadUnsupported()) return
|
||||
|
||||
await myAccountPage.playPlaylist()
|
||||
|
||||
await videoWatchPage.waitWatchVideoName(video2Name, 40 * 1000)
|
||||
})
|
||||
|
||||
it('Should watch the Web Video playlist in the embed', async () => {
|
||||
if (isUploadUnsupported()) return
|
||||
|
||||
const accessToken = await browser.execute(`return window.localStorage.getItem('access_token');`)
|
||||
const refreshToken = await browser.execute(`return window.localStorage.getItem('refresh_token');`)
|
||||
|
||||
await myAccountPage.goOnAssociatedPlaylistEmbed()
|
||||
|
||||
await playerPage.waitUntilPlayerWrapper()
|
||||
|
||||
console.log('Will set %s and %s tokens in local storage.', accessToken, refreshToken)
|
||||
|
||||
await browser.execute(`window.localStorage.setItem('access_token', '${accessToken}');`)
|
||||
await browser.execute(`window.localStorage.setItem('refresh_token', '${refreshToken}');`)
|
||||
await browser.execute(`window.localStorage.setItem('token_type', 'Bearer');`)
|
||||
|
||||
await browser.refresh()
|
||||
|
||||
await playerPage.playVideo()
|
||||
|
||||
await playerPage.waitUntilPlaylistInfo('2/2', 30 * 1000)
|
||||
})
|
||||
|
||||
it('Should watch the HLS playlist in the embed', async () => {
|
||||
await videoWatchPage.goOnP2PMediaLoaderPlaylistEmbed()
|
||||
|
||||
await playerPage.playVideo()
|
||||
|
||||
await playerPage.waitUntilPlaylistInfo('2/2', 30 * 1000)
|
||||
})
|
||||
|
||||
it('Should delete the video 2', async () => {
|
||||
if (isUploadUnsupported()) return
|
||||
|
||||
// Go to the dev website
|
||||
await go(videoWatchUrl)
|
||||
|
||||
await myAccountPage.navigateToMyVideos()
|
||||
|
||||
await myAccountPage.removeVideo(video2Name)
|
||||
await myAccountPage.validRemove()
|
||||
|
||||
await browser.waitUntil(async () => {
|
||||
const count = await myAccountPage.countVideos([ videoName, video2Name ])
|
||||
|
||||
return count === 1
|
||||
})
|
||||
})
|
||||
|
||||
it('Should delete the first video', async () => {
|
||||
if (isUploadUnsupported()) return
|
||||
|
||||
await myAccountPage.removeVideo(videoName)
|
||||
await myAccountPage.validRemove()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,95 @@
|
||||
import { LoginPage } from '../po/login.po'
|
||||
import { VideoPublishPage } from '../po/video-publish.po'
|
||||
import { VideoWatchPage } from '../po/video-watch.po'
|
||||
import { go, isMobileDevice, isSafari, prepareWebBrowser, waitServerUp } from '../utils'
|
||||
|
||||
describe('Custom server defaults', () => {
|
||||
let videoPublishPage: VideoPublishPage
|
||||
let loginPage: LoginPage
|
||||
let videoWatchPage: VideoWatchPage
|
||||
|
||||
before(async () => {
|
||||
await waitServerUp()
|
||||
|
||||
loginPage = new LoginPage(isMobileDevice())
|
||||
videoPublishPage = new VideoPublishPage()
|
||||
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
|
||||
|
||||
await prepareWebBrowser({ hidePrivacyConcerns: false })
|
||||
})
|
||||
|
||||
describe('Publish default values', function () {
|
||||
before(async function () {
|
||||
await loginPage.loginAsRootUser()
|
||||
})
|
||||
|
||||
it('Should upload a video with custom default values', async function () {
|
||||
await videoPublishPage.navigateTo()
|
||||
await videoPublishPage.uploadVideo('video.mp4')
|
||||
await videoPublishPage.validSecondStep('video')
|
||||
|
||||
await videoPublishPage.clickOnWatch()
|
||||
await videoWatchPage.waitWatchVideoName('video')
|
||||
|
||||
const videoUrl = await browser.getUrl()
|
||||
|
||||
expect(await videoWatchPage.getPrivacy()).toBe('Unlisted')
|
||||
expect(await videoWatchPage.getLicence()).toBe('Attribution - Non Commercial')
|
||||
expect(await videoWatchPage.areCommentsEnabled()).toBeFalsy()
|
||||
|
||||
// Owners can download their videos
|
||||
expect(await videoWatchPage.isDownloadEnabled()).toBeTruthy()
|
||||
|
||||
// Logout to see if the download enabled is correct for anonymous users
|
||||
await loginPage.logout()
|
||||
await browser.url(videoUrl)
|
||||
await videoWatchPage.waitWatchVideoName('video')
|
||||
|
||||
expect(await videoWatchPage.isDownloadEnabled()).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('P2P', function () {
|
||||
let videoUrl: string
|
||||
|
||||
async function goOnVideoWatchPage () {
|
||||
await go(videoUrl)
|
||||
await videoWatchPage.waitWatchVideoName('video')
|
||||
}
|
||||
|
||||
async function checkP2P (enabled: boolean) {
|
||||
await goOnVideoWatchPage()
|
||||
expect(await videoWatchPage.isPrivacyWarningDisplayed()).toEqual(enabled)
|
||||
|
||||
await videoWatchPage.goOnAssociatedEmbed()
|
||||
expect(await videoWatchPage.isEmbedWarningDisplayed()).toEqual(enabled)
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
await loginPage.loginAsRootUser()
|
||||
await videoPublishPage.navigateTo()
|
||||
await videoPublishPage.uploadVideo('video2.mp4')
|
||||
await videoPublishPage.setAsPublic()
|
||||
await videoPublishPage.validSecondStep('video')
|
||||
|
||||
await videoPublishPage.clickOnWatch()
|
||||
await videoWatchPage.waitWatchVideoName('video')
|
||||
|
||||
videoUrl = await browser.getUrl()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
await goOnVideoWatchPage()
|
||||
})
|
||||
|
||||
it('Should have P2P disabled for a logged in user', async function () {
|
||||
await checkP2P(false)
|
||||
})
|
||||
|
||||
it('Should have P2P disabled for anonymous users', async function () {
|
||||
await loginPage.logout()
|
||||
|
||||
await checkP2P(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
393
client/e2e/src/suites-local/nsfw.e2e-spec.ts
Normal file
393
client/e2e/src/suites-local/nsfw.e2e-spec.ts
Normal file
@@ -0,0 +1,393 @@
|
||||
import { NSFWPolicyType } from '@peertube/peertube-models'
|
||||
import { AdminConfigPage } from '../po/admin-config.po'
|
||||
import { AdminUserPage } from '../po/admin-user.po'
|
||||
import { AnonymousSettingsPage } from '../po/anonymous-settings.po'
|
||||
import { LoginPage } from '../po/login.po'
|
||||
import { MyAccountPage } from '../po/my-account.po'
|
||||
import { PlayerPage } from '../po/player.po'
|
||||
import { VideoListPage } from '../po/video-list.po'
|
||||
import { VideoPublishPage } from '../po/video-publish.po'
|
||||
import { VideoSearchPage } from '../po/video-search.po'
|
||||
import { VideoWatchPage } from '../po/video-watch.po'
|
||||
import { getScreenshotPath, go, isMobileDevice, isSafari, prepareWebBrowser, waitServerUp } from '../utils'
|
||||
|
||||
describe('NSFW', () => {
|
||||
let videoListPage: VideoListPage
|
||||
let videoPublishPage: VideoPublishPage
|
||||
let adminConfigPage: AdminConfigPage
|
||||
let loginPage: LoginPage
|
||||
let adminUserPage: AdminUserPage
|
||||
let myAccountPage: MyAccountPage
|
||||
let videoSearchPage: VideoSearchPage
|
||||
let videoWatchPage: VideoWatchPage
|
||||
let playerPage: PlayerPage
|
||||
let anonymousSettingsPage: AnonymousSettingsPage
|
||||
|
||||
const seed = Math.random()
|
||||
const nsfwVideo = seed + ' - nsfw'
|
||||
const violentVideo = seed + ' - violent'
|
||||
const normalVideo = seed + ' - normal'
|
||||
|
||||
let videoUrl: string
|
||||
|
||||
async function checkVideo (options: {
|
||||
policy: NSFWPolicyType
|
||||
videoName: string
|
||||
nsfwTooltip?: string
|
||||
}) {
|
||||
const { policy, videoName, nsfwTooltip } = options
|
||||
|
||||
if (policy === 'do_not_list') {
|
||||
expect(await videoListPage.isVideoDisplayed(videoName)).toBeFalsy()
|
||||
} else if (policy === 'warn') {
|
||||
expect(await videoListPage.isVideoDisplayed(videoName)).toBeTruthy()
|
||||
expect(await videoListPage.isVideoBlurred(videoName)).toBeFalsy()
|
||||
expect(await videoListPage.hasVideoWarning(videoName)).toBeTruthy()
|
||||
} else if (policy === 'blur') {
|
||||
expect(await videoListPage.isVideoDisplayed(videoName)).toBeTruthy()
|
||||
expect(await videoListPage.isVideoBlurred(videoName)).toBeTruthy()
|
||||
expect(await videoListPage.hasVideoWarning(videoName)).toBeTruthy()
|
||||
} else { // Display
|
||||
expect(await videoListPage.isVideoDisplayed(videoName)).toBeTruthy()
|
||||
expect(await videoListPage.isVideoBlurred(videoName)).toBeFalsy()
|
||||
expect(await videoListPage.hasVideoWarning(videoName)).toBeFalsy()
|
||||
}
|
||||
|
||||
if (nsfwTooltip) {
|
||||
await videoListPage.expectVideoNSFWTooltip(videoName, nsfwTooltip)
|
||||
}
|
||||
}
|
||||
|
||||
async function checkFilterText (policy: NSFWPolicyType) {
|
||||
const pagesWithFilters = [
|
||||
videoListPage.goOnRootAccount.bind(videoListPage),
|
||||
videoListPage.goOnBrowseVideos.bind(videoListPage),
|
||||
videoListPage.goOnRootChannel.bind(videoListPage)
|
||||
]
|
||||
|
||||
for (const goOnPage of pagesWithFilters) {
|
||||
await goOnPage()
|
||||
|
||||
const filterText = await videoListPage.getNSFWFilterText()
|
||||
|
||||
if (policy === 'do_not_list') {
|
||||
expect(filterText).toContain('hidden')
|
||||
} else if (policy === 'warn') {
|
||||
expect(filterText).toContain('warned')
|
||||
} else if (policy === 'blur') {
|
||||
expect(filterText).toContain('blurred')
|
||||
} else {
|
||||
expect(filterText).toContain('displayed')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function checkCommonVideoListPages (policy: NSFWPolicyType, videos: string[], nsfwTooltip?: string) {
|
||||
const pages = [
|
||||
videoListPage.goOnRootAccount.bind(videoListPage),
|
||||
videoListPage.goOnBrowseVideos.bind(videoListPage),
|
||||
videoListPage.goOnRootChannel.bind(videoListPage),
|
||||
videoListPage.goOnRootAccountChannels.bind(videoListPage),
|
||||
videoListPage.goOnHomepage.bind(videoListPage)
|
||||
]
|
||||
|
||||
for (const goOnPage of pages) {
|
||||
await goOnPage()
|
||||
|
||||
for (const video of videos) {
|
||||
await browser.saveScreenshot(getScreenshotPath('before-nsfw-test.png'))
|
||||
await checkVideo({ policy, videoName: video, nsfwTooltip })
|
||||
}
|
||||
}
|
||||
|
||||
for (const video of videos) {
|
||||
await videoSearchPage.search(video)
|
||||
|
||||
await browser.saveScreenshot(getScreenshotPath('before-nsfw-test.png'))
|
||||
await checkVideo({ policy, videoName: video, nsfwTooltip })
|
||||
}
|
||||
}
|
||||
|
||||
async function updateAdminNSFW (nsfw: NSFWPolicyType) {
|
||||
await adminConfigPage.updateNSFWSetting(nsfw)
|
||||
await adminConfigPage.save()
|
||||
}
|
||||
|
||||
async function updateUserNSFW (nsfw: NSFWPolicyType, loggedIn: boolean) {
|
||||
if (loggedIn) {
|
||||
await myAccountPage.navigateToMySettings()
|
||||
await myAccountPage.updateNSFW(nsfw)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await anonymousSettingsPage.openSettings()
|
||||
await anonymousSettingsPage.updateNSFW(nsfw)
|
||||
await anonymousSettingsPage.closeSettings()
|
||||
}
|
||||
|
||||
async function updateUserViolentNSFW (nsfw: NSFWPolicyType, loggedIn: boolean) {
|
||||
if (loggedIn) {
|
||||
await myAccountPage.navigateToMySettings()
|
||||
await myAccountPage.updateViolentFlag(nsfw)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await anonymousSettingsPage.openSettings()
|
||||
await anonymousSettingsPage.updateViolentFlag(nsfw)
|
||||
await anonymousSettingsPage.closeSettings()
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
await waitServerUp()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
videoListPage = new VideoListPage(isMobileDevice(), isSafari())
|
||||
adminConfigPage = new AdminConfigPage()
|
||||
loginPage = new LoginPage(isMobileDevice())
|
||||
adminUserPage = new AdminUserPage()
|
||||
videoPublishPage = new VideoPublishPage()
|
||||
myAccountPage = new MyAccountPage()
|
||||
videoSearchPage = new VideoSearchPage()
|
||||
playerPage = new PlayerPage()
|
||||
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
|
||||
anonymousSettingsPage = new AnonymousSettingsPage()
|
||||
|
||||
await prepareWebBrowser()
|
||||
})
|
||||
|
||||
describe('Preparation', function () {
|
||||
it('Should login and disable NSFW', async () => {
|
||||
await loginPage.loginAsRootUser()
|
||||
await updateUserNSFW('display', true)
|
||||
})
|
||||
|
||||
it('Should set the homepage', async () => {
|
||||
await adminConfigPage.updateHomepage('<peertube-videos-list data-sort="-publishedAt"></peertube-videos-list>')
|
||||
await adminConfigPage.save()
|
||||
})
|
||||
|
||||
it('Should create a user', async () => {
|
||||
await adminUserPage.createUser({
|
||||
username: 'user_' + seed,
|
||||
password: 'superpassword'
|
||||
})
|
||||
})
|
||||
|
||||
it('Should upload NSFW and normal videos', async () => {
|
||||
await videoPublishPage.navigateTo()
|
||||
await videoPublishPage.uploadVideo('video.mp4')
|
||||
await videoPublishPage.setAsNSFW()
|
||||
await videoPublishPage.validSecondStep(nsfwVideo)
|
||||
|
||||
await videoPublishPage.navigateTo()
|
||||
await videoPublishPage.uploadVideo('video.mp4')
|
||||
await videoPublishPage.setAsNSFW({ summary: 'bibi is violent', violent: true })
|
||||
await videoPublishPage.validSecondStep(violentVideo)
|
||||
|
||||
await videoPublishPage.navigateTo()
|
||||
await videoPublishPage.uploadVideo('video2.mp4')
|
||||
await videoPublishPage.validSecondStep(normalVideo)
|
||||
})
|
||||
|
||||
it('Should logout', async function () {
|
||||
await loginPage.logout()
|
||||
})
|
||||
})
|
||||
|
||||
describe('NSFW with an anonymous users using instance default', 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', [ nsfwVideo, violentVideo ])
|
||||
await checkCommonVideoListPages('display', [ normalVideo ])
|
||||
|
||||
await checkFilterText('do_not_list')
|
||||
})
|
||||
|
||||
it('Should correctly handle blur', async () => {
|
||||
await loginPage.loginAsRootUser()
|
||||
await updateAdminNSFW('blur')
|
||||
|
||||
await loginPage.logout()
|
||||
|
||||
await checkCommonVideoListPages('blur', [ nsfwVideo, violentVideo ])
|
||||
await checkCommonVideoListPages('display', [ normalVideo ])
|
||||
|
||||
await checkFilterText('blur')
|
||||
})
|
||||
|
||||
it('Should not autoplay the video and display a warning on watch/embed page', async function () {
|
||||
await videoListPage.clickOnVideo(nsfwVideo)
|
||||
await videoWatchPage.waitWatchVideoName(nsfwVideo)
|
||||
|
||||
videoUrl = await browser.getUrl()
|
||||
|
||||
const check = async () => {
|
||||
expect(await playerPage.getPlayButton().isDisplayed()).toBeTruthy()
|
||||
|
||||
expect(await playerPage.getNSFWContentText()).toContain('This video contains sensitive content')
|
||||
expect(await playerPage.getMoreNSFWInfoButton().isDisplayed()).toBeFalsy()
|
||||
expect(await playerPage.hasPoster()).toBeFalsy()
|
||||
}
|
||||
|
||||
await check()
|
||||
await videoWatchPage.goOnAssociatedEmbed()
|
||||
await check()
|
||||
})
|
||||
|
||||
it('Should correctly handle warn', async () => {
|
||||
await loginPage.loginAsRootUser()
|
||||
await updateAdminNSFW('warn')
|
||||
|
||||
await loginPage.logout()
|
||||
|
||||
await checkCommonVideoListPages('warn', [ nsfwVideo, violentVideo ])
|
||||
await checkCommonVideoListPages('display', [ normalVideo ])
|
||||
|
||||
await checkFilterText('warn')
|
||||
})
|
||||
|
||||
it('Should not autoplay the video and display a warning on watch/embed page', async function () {
|
||||
await videoListPage.clickOnVideo(violentVideo)
|
||||
await videoWatchPage.waitWatchVideoName(violentVideo)
|
||||
|
||||
const check = async () => {
|
||||
expect(await playerPage.getPlayButton().isDisplayed()).toBeTruthy()
|
||||
|
||||
expect(await playerPage.getNSFWContentText()).toContain('This video contains sensitive content')
|
||||
expect(await playerPage.hasPoster()).toBeTruthy()
|
||||
|
||||
const moreButton = playerPage.getMoreNSFWInfoButton()
|
||||
expect(await moreButton.isDisplayed()).toBeTruthy()
|
||||
|
||||
await moreButton.click()
|
||||
await playerPage.getNSFWDetailsContent().waitForDisplayed()
|
||||
|
||||
const moreContent = await playerPage.getNSFWDetailsContent().getText()
|
||||
expect(moreContent).toContain('Potentially violent content')
|
||||
expect(moreContent).toContain('bibi is violent')
|
||||
}
|
||||
|
||||
await check()
|
||||
await videoWatchPage.goOnAssociatedEmbed()
|
||||
await check()
|
||||
})
|
||||
|
||||
it('Should correctly handle display', async () => {
|
||||
await loginPage.loginAsRootUser()
|
||||
await updateAdminNSFW('display')
|
||||
|
||||
await loginPage.logout()
|
||||
|
||||
await checkCommonVideoListPages('display', [ nsfwVideo, violentVideo, normalVideo ])
|
||||
|
||||
await checkFilterText('display')
|
||||
})
|
||||
|
||||
it('Should autoplay the video on watch page', async function () {
|
||||
await videoListPage.clickOnVideo(nsfwVideo)
|
||||
await videoWatchPage.waitWatchVideoName(nsfwVideo)
|
||||
|
||||
expect(await playerPage.getPlayButton().isDisplayed()).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('NSFW settings', function () {
|
||||
function runSuite (loggedIn: boolean) {
|
||||
it('Should correctly handle do not list', async () => {
|
||||
await updateUserNSFW('do_not_list', loggedIn)
|
||||
|
||||
await checkCommonVideoListPages('do_not_list', [ nsfwVideo, violentVideo ])
|
||||
await checkCommonVideoListPages('display', [ normalVideo ])
|
||||
|
||||
await checkFilterText('do_not_list')
|
||||
})
|
||||
|
||||
it('Should use a confirm modal when viewing the video and watch the video', async function () {
|
||||
await go(videoUrl)
|
||||
|
||||
const confirmTitle = videoWatchPage.getModalTitleEl()
|
||||
await confirmTitle.waitForDisplayed()
|
||||
expect(await confirmTitle.getText()).toContain('Sensitive video')
|
||||
|
||||
await videoWatchPage.confirmModal()
|
||||
await videoWatchPage.waitWatchVideoName(nsfwVideo)
|
||||
})
|
||||
|
||||
it('Should correctly handle blur', async () => {
|
||||
await updateUserNSFW('blur', loggedIn)
|
||||
|
||||
await checkCommonVideoListPages('blur', [ nsfwVideo ], 'This video contains sensitive content')
|
||||
await checkCommonVideoListPages('blur', [ violentVideo ], 'This video contains sensitive content: violence')
|
||||
await checkCommonVideoListPages('display', [ normalVideo ])
|
||||
|
||||
await checkFilterText('blur')
|
||||
})
|
||||
|
||||
it('Should correctly handle warn', async () => {
|
||||
await updateUserNSFW('warn', loggedIn)
|
||||
|
||||
await checkCommonVideoListPages('warn', [ nsfwVideo ], 'This video contains sensitive content')
|
||||
await checkCommonVideoListPages('warn', [ violentVideo ], 'This video contains sensitive content: violence')
|
||||
await checkCommonVideoListPages('display', [ normalVideo ])
|
||||
|
||||
await checkFilterText('warn')
|
||||
})
|
||||
|
||||
it('Should correctly handle display', async () => {
|
||||
await updateUserNSFW('display', loggedIn)
|
||||
|
||||
await checkCommonVideoListPages('display', [ nsfwVideo, violentVideo, normalVideo ])
|
||||
|
||||
await checkFilterText('display')
|
||||
})
|
||||
|
||||
it('Should update the setting to blur violent video with display NSFW setting', async () => {
|
||||
await updateUserViolentNSFW('blur', loggedIn)
|
||||
|
||||
await checkCommonVideoListPages('display', [ nsfwVideo, normalVideo ])
|
||||
await checkCommonVideoListPages('blur', [ violentVideo ])
|
||||
})
|
||||
|
||||
it('Should update the setting to hide NSFW videos but warn violent videos', async () => {
|
||||
await updateUserNSFW('do_not_list', loggedIn)
|
||||
await updateUserViolentNSFW('warn', loggedIn)
|
||||
|
||||
await checkCommonVideoListPages('display', [ normalVideo ])
|
||||
await checkCommonVideoListPages('warn', [ violentVideo ])
|
||||
await checkCommonVideoListPages('do_not_list', [ nsfwVideo ])
|
||||
})
|
||||
|
||||
it('Should update the setting to blur NSFW videos and hide violent videos', async () => {
|
||||
await updateUserNSFW('blur', loggedIn)
|
||||
await updateUserViolentNSFW('do_not_list', loggedIn)
|
||||
|
||||
await checkCommonVideoListPages('display', [ normalVideo ])
|
||||
await checkCommonVideoListPages('do_not_list', [ violentVideo ])
|
||||
await checkCommonVideoListPages('blur', [ nsfwVideo ])
|
||||
})
|
||||
}
|
||||
|
||||
describe('NSFW with an anonymous user', function () {
|
||||
runSuite(false)
|
||||
})
|
||||
|
||||
describe('NSFW with a logged in users', function () {
|
||||
before(async () => {
|
||||
await loginPage.login({ username: 'user_' + seed, password: 'superpassword' })
|
||||
})
|
||||
|
||||
runSuite(true)
|
||||
|
||||
after(async () => {
|
||||
await loginPage.logout()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
141
client/e2e/src/suites-local/page-crash.e2e-spec.ts
Normal file
141
client/e2e/src/suites-local/page-crash.e2e-spec.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { AdminConfigPage } from '../po/admin-config.po'
|
||||
import { LoginPage } from '../po/login.po'
|
||||
import { VideoPublishPage } from '../po/video-publish.po'
|
||||
import { VideoWatchPage } from '../po/video-watch.po'
|
||||
import { getScreenshotPath, go, isMobileDevice, isSafari, prepareWebBrowser, selectCustomSelect, waitServerUp } from '../utils'
|
||||
|
||||
// These tests help to notice crash with invalid translated strings
|
||||
describe('Page crash', () => {
|
||||
let videoPublishPage: VideoPublishPage
|
||||
let loginPage: LoginPage
|
||||
let videoWatchPage: VideoWatchPage
|
||||
let adminConfigPage: AdminConfigPage
|
||||
|
||||
const languages = [
|
||||
'العربية',
|
||||
'Català',
|
||||
'Čeština',
|
||||
'Deutsch',
|
||||
'ελληνικά',
|
||||
'Esperanto',
|
||||
'Español',
|
||||
'Euskara',
|
||||
'فارسی',
|
||||
'Suomi',
|
||||
'Français',
|
||||
'Gàidhlig',
|
||||
'Galego',
|
||||
'Hrvatski',
|
||||
'Magyar',
|
||||
'Íslenska',
|
||||
'Italiano',
|
||||
'日本語',
|
||||
'Taqbaylit',
|
||||
'Norsk bokmål',
|
||||
'Nederlands',
|
||||
'Norsk nynorsk',
|
||||
'Occitan',
|
||||
'Polski',
|
||||
'Português (Brasil)',
|
||||
'Português (Portugal)',
|
||||
'Pусский',
|
||||
'Slovenčina',
|
||||
'Shqip',
|
||||
'Svenska',
|
||||
'ไทย',
|
||||
'Toki Pona',
|
||||
'Türkçe',
|
||||
'украї́нська мо́ва',
|
||||
'Tiếng Việt',
|
||||
'简体中文(中国)',
|
||||
'繁體中文(台灣)'
|
||||
]
|
||||
|
||||
before(async () => {
|
||||
await waitServerUp()
|
||||
|
||||
adminConfigPage = new AdminConfigPage()
|
||||
loginPage = new LoginPage(isMobileDevice())
|
||||
videoPublishPage = new VideoPublishPage()
|
||||
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
|
||||
|
||||
await prepareWebBrowser()
|
||||
})
|
||||
|
||||
for (const language of languages) {
|
||||
describe('For language: ' + language, () => {
|
||||
describe('Logged in user', () => {
|
||||
before(async () => {
|
||||
await loginPage.loginAsRootUser()
|
||||
})
|
||||
|
||||
it('Should change the language', async function () {
|
||||
await go('/')
|
||||
|
||||
await $('.settings-button').waitForClickable()
|
||||
await $('.settings-button').click()
|
||||
|
||||
await selectCustomSelect('language', language)
|
||||
|
||||
await $('my-user-interface-settings .primary-button').waitForClickable()
|
||||
await $('my-user-interface-settings .primary-button').click()
|
||||
})
|
||||
|
||||
it('Should upload and watch a video', async function () {
|
||||
await videoPublishPage.navigateTo()
|
||||
await videoPublishPage.uploadVideo('video3.mp4')
|
||||
await videoPublishPage.validSecondStep('video')
|
||||
|
||||
await videoPublishPage.clickOnWatch()
|
||||
await videoWatchPage.waitWatchVideoName('video')
|
||||
})
|
||||
|
||||
it('Should set a homepage', async function () {
|
||||
await adminConfigPage.updateHomepage('My custom homepage content')
|
||||
await adminConfigPage.save()
|
||||
|
||||
// All tests
|
||||
await go('/home')
|
||||
|
||||
await $('*=My custom homepage content').waitForDisplayed()
|
||||
})
|
||||
|
||||
it('Should go on overview page and not crash', async function () {
|
||||
await $('a[href="/videos/overview"]').waitForClickable()
|
||||
await $('a[href="/videos/overview"]').click()
|
||||
|
||||
await $('my-video-overview').waitForExist()
|
||||
})
|
||||
|
||||
it('Should go on videos from subscriptions page', async function () {
|
||||
await $('a[href="/videos/subscriptions"]').waitForClickable()
|
||||
await $('a[href="/videos/subscriptions"]').click()
|
||||
|
||||
await $('my-videos-user-subscriptions').waitForExist()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Anonymous user', () => {
|
||||
before(async () => {
|
||||
await adminConfigPage.toggleSignup(true)
|
||||
|
||||
await adminConfigPage.save()
|
||||
|
||||
await loginPage.logout()
|
||||
await browser.refresh()
|
||||
})
|
||||
|
||||
it('Should go on signup page', async function () {
|
||||
await $('.create-account-button').waitForClickable()
|
||||
await $('.create-account-button').click()
|
||||
|
||||
await $('.callout-content > h4').waitForExist()
|
||||
})
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await browser.saveScreenshot(getScreenshotPath(`after-page-crash-test-${language}.png`))
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
83
client/e2e/src/suites-local/player-settings.e2e-spec.ts
Normal file
83
client/e2e/src/suites-local/player-settings.e2e-spec.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { AnonymousSettingsPage } from '../po/anonymous-settings.po'
|
||||
import { LoginPage } from '../po/login.po'
|
||||
import { MyAccountPage } from '../po/my-account.po'
|
||||
import { VideoPublishPage } from '../po/video-publish.po'
|
||||
import { VideoWatchPage } from '../po/video-watch.po'
|
||||
import { go, isMobileDevice, isSafari, prepareWebBrowser, waitServerUp } from '../utils'
|
||||
|
||||
describe('Player settings', () => {
|
||||
let videoPublishPage: VideoPublishPage
|
||||
let loginPage: LoginPage
|
||||
let videoWatchPage: VideoWatchPage
|
||||
let myAccountPage: MyAccountPage
|
||||
let anonymousSettingsPage: AnonymousSettingsPage
|
||||
|
||||
before(async () => {
|
||||
await waitServerUp()
|
||||
|
||||
loginPage = new LoginPage(isMobileDevice())
|
||||
videoPublishPage = new VideoPublishPage()
|
||||
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
|
||||
myAccountPage = new MyAccountPage()
|
||||
anonymousSettingsPage = new AnonymousSettingsPage()
|
||||
|
||||
await prepareWebBrowser({ hidePrivacyConcerns: false })
|
||||
})
|
||||
|
||||
describe('P2P', function () {
|
||||
let videoUrl: string
|
||||
|
||||
async function goOnVideoWatchPage () {
|
||||
await go(videoUrl)
|
||||
await videoWatchPage.waitWatchVideoName('video')
|
||||
}
|
||||
|
||||
async function checkP2P (enabled: boolean) {
|
||||
await goOnVideoWatchPage()
|
||||
expect(await videoWatchPage.isPrivacyWarningDisplayed()).toEqual(enabled)
|
||||
|
||||
await videoWatchPage.goOnAssociatedEmbed()
|
||||
expect(await videoWatchPage.isEmbedWarningDisplayed()).toEqual(enabled)
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
await loginPage.loginAsRootUser()
|
||||
await videoPublishPage.navigateTo()
|
||||
await videoPublishPage.uploadVideo('video.mp4')
|
||||
await videoPublishPage.validSecondStep('video')
|
||||
|
||||
await videoPublishPage.clickOnWatch()
|
||||
await videoWatchPage.waitWatchVideoName('video')
|
||||
|
||||
videoUrl = await browser.getUrl()
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
await goOnVideoWatchPage()
|
||||
})
|
||||
|
||||
it('Should have P2P enabled for a logged in user', async function () {
|
||||
await checkP2P(true)
|
||||
})
|
||||
|
||||
it('Should disable P2P for a logged in user', async function () {
|
||||
await myAccountPage.navigateToMySettings()
|
||||
await myAccountPage.clickOnP2PCheckbox()
|
||||
|
||||
await checkP2P(false)
|
||||
})
|
||||
|
||||
it('Should have P2P enabled for anonymous users', async function () {
|
||||
await loginPage.logout()
|
||||
|
||||
await checkP2P(true)
|
||||
})
|
||||
|
||||
it('Should disable P2P for an anonymous user', async function () {
|
||||
await anonymousSettingsPage.openSettings()
|
||||
await anonymousSettingsPage.clickOnP2PCheckbox()
|
||||
|
||||
await checkP2P(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
86
client/e2e/src/suites-local/plugins.e2e-spec.ts
Normal file
86
client/e2e/src/suites-local/plugins.e2e-spec.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { AdminPluginPage } from '../po/admin-plugin.po'
|
||||
import { LoginPage } from '../po/login.po'
|
||||
import { VideoPublishPage } from '../po/video-publish.po'
|
||||
import { getCheckbox, isMobileDevice, prepareWebBrowser, waitServerUp } from '../utils'
|
||||
|
||||
describe('Plugins', () => {
|
||||
let videoPublishPage: VideoPublishPage
|
||||
let loginPage: LoginPage
|
||||
let adminPluginPage: AdminPluginPage
|
||||
|
||||
function getPluginCheckbox () {
|
||||
return getCheckbox('hello-world-field-4')
|
||||
}
|
||||
|
||||
async function expectSubmitError (hasError: boolean) {
|
||||
await videoPublishPage.clickOnSave()
|
||||
|
||||
await $('.form-error*=Should be enabled').waitForDisplayed({ reverse: !hasError })
|
||||
await $('li*=Should be enabled').waitForDisplayed({ reverse: !hasError })
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
await waitServerUp()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
loginPage = new LoginPage(isMobileDevice())
|
||||
videoPublishPage = new VideoPublishPage()
|
||||
adminPluginPage = new AdminPluginPage()
|
||||
|
||||
await prepareWebBrowser()
|
||||
})
|
||||
|
||||
it('Should install hello world plugin', async () => {
|
||||
await loginPage.loginAsRootUser()
|
||||
|
||||
await adminPluginPage.navigateToPluginSearch()
|
||||
await adminPluginPage.search('hello-world')
|
||||
await adminPluginPage.installHelloWorld()
|
||||
await browser.refresh()
|
||||
})
|
||||
|
||||
it('Should have checkbox in video edit page', async () => {
|
||||
await videoPublishPage.navigateTo()
|
||||
await videoPublishPage.uploadVideo('video.mp4')
|
||||
|
||||
const el = () => $('span=Super field 4 in main tab')
|
||||
await el().waitForDisplayed()
|
||||
|
||||
// Only displayed if the video is public
|
||||
await videoPublishPage.setAsPrivate()
|
||||
await el().waitForDisplayed({ reverse: true })
|
||||
|
||||
await videoPublishPage.setAsPublic()
|
||||
await el().waitForDisplayed()
|
||||
|
||||
const checkbox = await getPluginCheckbox()
|
||||
expect(await checkbox.isDisplayed()).toBeTruthy()
|
||||
|
||||
await expectSubmitError(true)
|
||||
})
|
||||
|
||||
it('Should check the checkbox and be able to submit the video', async function () {
|
||||
const checkbox = await getPluginCheckbox()
|
||||
|
||||
await checkbox.waitForClickable()
|
||||
await checkbox.click()
|
||||
|
||||
await expectSubmitError(false)
|
||||
})
|
||||
|
||||
it('Should uncheck the checkbox and not be able to submit the video', async function () {
|
||||
const checkbox = await getPluginCheckbox()
|
||||
|
||||
await checkbox.waitForClickable()
|
||||
await checkbox.click()
|
||||
|
||||
await expectSubmitError(true)
|
||||
})
|
||||
|
||||
it('Should change the privacy and should hide the checkbox', async function () {
|
||||
await videoPublishPage.setAsPrivate()
|
||||
|
||||
await expectSubmitError(false)
|
||||
})
|
||||
})
|
||||
57
client/e2e/src/suites-local/publish-live.e2e-spec.ts
Normal file
57
client/e2e/src/suites-local/publish-live.e2e-spec.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { AdminConfigPage } from '../po/admin-config.po'
|
||||
import { LoginPage } from '../po/login.po'
|
||||
import { VideoPublishPage } from '../po/video-publish.po'
|
||||
import { VideoWatchPage } from '../po/video-watch.po'
|
||||
import { isMobileDevice, isSafari, prepareWebBrowser, waitServerUp } from '../utils'
|
||||
|
||||
describe('Publish live', function () {
|
||||
let videoPublishPage: VideoPublishPage
|
||||
let loginPage: LoginPage
|
||||
let adminConfigPage: AdminConfigPage
|
||||
let videoWatchPage: VideoWatchPage
|
||||
|
||||
before(async () => {
|
||||
await waitServerUp()
|
||||
|
||||
loginPage = new LoginPage(isMobileDevice())
|
||||
videoPublishPage = new VideoPublishPage()
|
||||
adminConfigPage = new AdminConfigPage()
|
||||
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
|
||||
|
||||
await prepareWebBrowser()
|
||||
|
||||
await loginPage.loginAsRootUser()
|
||||
})
|
||||
|
||||
it('Should enable live', async function () {
|
||||
await adminConfigPage.toggleLive(true)
|
||||
await adminConfigPage.save()
|
||||
})
|
||||
|
||||
it('Should create a classic permanent live', async function () {
|
||||
await videoPublishPage.navigateTo('Go live')
|
||||
|
||||
await videoPublishPage.publishLive()
|
||||
await videoPublishPage.validSecondStep('Permanent live test')
|
||||
|
||||
expect(await videoPublishPage.getLiveState()).toEqual('permanent')
|
||||
|
||||
await videoPublishPage.clickOnWatch()
|
||||
|
||||
await videoWatchPage.waitWatchVideoName('Permanent live test')
|
||||
})
|
||||
|
||||
it('Should create a permanent live and update it to a normal live', async function () {
|
||||
await videoPublishPage.navigateTo('Go live')
|
||||
|
||||
await videoPublishPage.publishLive()
|
||||
await videoPublishPage.setNormalLive()
|
||||
await videoPublishPage.validSecondStep('Normal live test')
|
||||
await videoPublishPage.clickOnWatch()
|
||||
|
||||
await videoWatchPage.waitWatchVideoName('Normal live test')
|
||||
await videoWatchPage.clickOnManage()
|
||||
|
||||
expect(await videoPublishPage.getLiveState()).toEqual('normal')
|
||||
})
|
||||
})
|
||||
79
client/e2e/src/suites-local/publish.e2e-spec.ts
Normal file
79
client/e2e/src/suites-local/publish.e2e-spec.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { LoginPage } from '../po/login.po'
|
||||
import { VideoPublishPage } from '../po/video-publish.po'
|
||||
import { VideoWatchPage } from '../po/video-watch.po'
|
||||
import { isMobileDevice, isSafari, prepareWebBrowser, waitServerUp } from '../utils'
|
||||
|
||||
describe('Publish video', () => {
|
||||
let videoPublishPage: VideoPublishPage
|
||||
let loginPage: LoginPage
|
||||
let videoWatchPage: VideoWatchPage
|
||||
|
||||
before(async () => {
|
||||
await waitServerUp()
|
||||
|
||||
loginPage = new LoginPage(isMobileDevice())
|
||||
videoPublishPage = new VideoPublishPage()
|
||||
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
|
||||
|
||||
await prepareWebBrowser()
|
||||
|
||||
await loginPage.loginAsRootUser()
|
||||
})
|
||||
|
||||
describe('Default upload values', function () {
|
||||
it('Should have default video values', async function () {
|
||||
await videoPublishPage.navigateTo()
|
||||
await videoPublishPage.uploadVideo('video3.mp4')
|
||||
await videoPublishPage.validSecondStep('video')
|
||||
|
||||
await videoPublishPage.clickOnWatch()
|
||||
await videoWatchPage.waitWatchVideoName('video')
|
||||
|
||||
expect(await videoWatchPage.getPrivacy()).toBe('Public')
|
||||
expect(await videoWatchPage.getLicence()).toBe('Unknown')
|
||||
expect(await videoWatchPage.isDownloadEnabled()).toBeTruthy()
|
||||
expect(await videoWatchPage.areCommentsEnabled()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Common', function () {
|
||||
it('Should upload a video and on refresh being redirected to the manage page', async function () {
|
||||
await videoPublishPage.navigateTo()
|
||||
await videoPublishPage.uploadVideo('video.mp4')
|
||||
await videoPublishPage.validSecondStep('first video')
|
||||
|
||||
await videoPublishPage.refresh('first video')
|
||||
})
|
||||
|
||||
it('Should upload a video and schedule upload date', async function () {
|
||||
await videoPublishPage.navigateTo()
|
||||
await videoPublishPage.uploadVideo('video.mp4')
|
||||
|
||||
await videoPublishPage.scheduleUpload()
|
||||
await videoPublishPage.validSecondStep('scheduled')
|
||||
|
||||
await videoPublishPage.refresh('scheduled')
|
||||
|
||||
expect(videoPublishPage.getScheduleInput()).toBeDisplayed()
|
||||
|
||||
const nextDay = new Date()
|
||||
nextDay.setDate(1)
|
||||
nextDay.setMonth(nextDay.getMonth() + 1)
|
||||
|
||||
const inputDate = new Date(await videoPublishPage.getScheduleInput().getValue())
|
||||
expect(inputDate.getDate()).toEqual(nextDay.getDate())
|
||||
expect(inputDate.getMonth()).toEqual(nextDay.getMonth())
|
||||
expect(inputDate.getFullYear()).toEqual(nextDay.getFullYear())
|
||||
})
|
||||
})
|
||||
|
||||
describe('Import', function () {
|
||||
it('Should import a video and on refresh being redirected to the manage page', async function () {
|
||||
await videoPublishPage.navigateTo()
|
||||
await videoPublishPage.importVideo()
|
||||
await videoPublishPage.validSecondStep('second video')
|
||||
|
||||
await videoPublishPage.refresh('second video')
|
||||
})
|
||||
})
|
||||
})
|
||||
408
client/e2e/src/suites-local/signup.e2e-spec.ts
Normal file
408
client/e2e/src/suites-local/signup.e2e-spec.ts
Normal file
@@ -0,0 +1,408 @@
|
||||
import { AdminConfigPage } from '../po/admin-config.po'
|
||||
import { AdminRegistrationPage } from '../po/admin-registration.po'
|
||||
import { LoginPage } from '../po/login.po'
|
||||
import { SignupPage } from '../po/signup.po'
|
||||
import {
|
||||
browserSleep,
|
||||
findEmailTo,
|
||||
getEmailPort,
|
||||
getScreenshotPath,
|
||||
getVerificationLink,
|
||||
go,
|
||||
isMobileDevice,
|
||||
MockSMTPServer,
|
||||
prepareWebBrowser,
|
||||
waitServerUp
|
||||
} from '../utils'
|
||||
|
||||
function checkEndMessage (options: {
|
||||
message: string
|
||||
requiresEmailVerification: boolean
|
||||
requiresApproval: boolean
|
||||
afterEmailVerification: boolean
|
||||
}) {
|
||||
const { message, requiresApproval, requiresEmailVerification, afterEmailVerification } = options
|
||||
|
||||
{
|
||||
const created = 'account has been created'
|
||||
const request = 'account request has been sent'
|
||||
|
||||
if (requiresApproval) {
|
||||
expect(message).toContain(request)
|
||||
expect(message).not.toContain(created)
|
||||
} else {
|
||||
expect(message).not.toContain(request)
|
||||
expect(message).toContain(created)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const checkEmail = 'Check your email'
|
||||
|
||||
if (requiresEmailVerification) {
|
||||
expect(message).toContain(checkEmail)
|
||||
} else {
|
||||
expect(message).not.toContain(checkEmail)
|
||||
|
||||
const moderatorsApproval = 'moderator will check your registration request'
|
||||
if (requiresApproval) {
|
||||
expect(message).toContain(moderatorsApproval)
|
||||
} else {
|
||||
expect(message).not.toContain(moderatorsApproval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const emailVerified = 'email has been verified'
|
||||
|
||||
if (afterEmailVerification) {
|
||||
expect(message).toContain(emailVerified)
|
||||
} else {
|
||||
expect(message).not.toContain(emailVerified)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('Signup', () => {
|
||||
let loginPage: LoginPage
|
||||
let adminConfigPage: AdminConfigPage
|
||||
let signupPage: SignupPage
|
||||
let adminRegistrationPage: AdminRegistrationPage
|
||||
|
||||
async function prepareSignup (options: {
|
||||
enabled: boolean
|
||||
requiresApproval?: boolean
|
||||
requiresEmailVerification?: boolean
|
||||
}) {
|
||||
await loginPage.loginAsRootUser()
|
||||
|
||||
// Ensure we change the state of the form to "dirty" so we can save the form
|
||||
await adminConfigPage.toggleSignup(options.enabled)
|
||||
|
||||
if (options.enabled) {
|
||||
if (options.requiresApproval !== undefined) {
|
||||
await adminConfigPage.toggleSignupApproval(options.requiresApproval)
|
||||
}
|
||||
|
||||
if (options.requiresEmailVerification !== undefined) {
|
||||
await adminConfigPage.toggleSignupEmailVerification(options.requiresEmailVerification)
|
||||
}
|
||||
}
|
||||
|
||||
await adminConfigPage.save()
|
||||
|
||||
await loginPage.logout()
|
||||
await browser.refresh()
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
await waitServerUp()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
loginPage = new LoginPage(isMobileDevice())
|
||||
adminConfigPage = new AdminConfigPage()
|
||||
signupPage = new SignupPage()
|
||||
adminRegistrationPage = new AdminRegistrationPage()
|
||||
|
||||
await prepareWebBrowser()
|
||||
})
|
||||
|
||||
describe('Signup disabled', function () {
|
||||
it('Should disable signup', async () => {
|
||||
await prepareSignup({ enabled: false })
|
||||
|
||||
await expect(signupPage.getRegisterMenuButton()).not.toBeDisplayed()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Email verification disabled', function () {
|
||||
describe('Direct registration', function () {
|
||||
it('Should enable signup without approval', async () => {
|
||||
await prepareSignup({ enabled: true, requiresApproval: false, requiresEmailVerification: false })
|
||||
|
||||
await signupPage.getRegisterMenuButton().waitForDisplayed()
|
||||
})
|
||||
|
||||
it('Should go on signup page', async function () {
|
||||
await signupPage.clickOnRegisterButton()
|
||||
})
|
||||
|
||||
it('Should validate the first step (about page)', async function () {
|
||||
await signupPage.validateStep()
|
||||
})
|
||||
|
||||
it('Should validate the second step (terms)', async function () {
|
||||
await signupPage.checkTerms()
|
||||
await signupPage.validateStep()
|
||||
})
|
||||
|
||||
it('Should validate the third step (account)', async function () {
|
||||
await signupPage.fillAccountStep({ username: 'user_1', displayName: 'user_1_dn' })
|
||||
|
||||
await signupPage.validateStep()
|
||||
})
|
||||
|
||||
it('Should validate the third step (channel)', async function () {
|
||||
await signupPage.fillChannelStep({ name: 'user_1_channel' })
|
||||
|
||||
await signupPage.validateStep()
|
||||
})
|
||||
|
||||
it('Should be logged in', async function () {
|
||||
await loginPage.ensureIsLoggedInAs('user_1_dn')
|
||||
})
|
||||
|
||||
it('Should have a valid end message', async function () {
|
||||
const message = await signupPage.getEndMessage()
|
||||
|
||||
checkEndMessage({
|
||||
message,
|
||||
requiresEmailVerification: false,
|
||||
requiresApproval: false,
|
||||
afterEmailVerification: false
|
||||
})
|
||||
|
||||
await browser.saveScreenshot(getScreenshotPath('direct-without-email.png'))
|
||||
|
||||
await loginPage.logout()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Registration with approval', function () {
|
||||
it('Should enable signup with approval', async () => {
|
||||
await prepareSignup({ enabled: true, requiresApproval: true, requiresEmailVerification: false })
|
||||
|
||||
await signupPage.getRegisterMenuButton().waitForDisplayed()
|
||||
})
|
||||
|
||||
it('Should go on signup page', async function () {
|
||||
await signupPage.clickOnRegisterButton()
|
||||
})
|
||||
|
||||
it('Should validate the first step (about page)', async function () {
|
||||
await signupPage.validateStep()
|
||||
})
|
||||
|
||||
it('Should validate the second step (terms)', async function () {
|
||||
await signupPage.checkTerms()
|
||||
await signupPage.fillRegistrationReason('my super reason')
|
||||
await signupPage.validateStep()
|
||||
})
|
||||
|
||||
it('Should validate the third step (account)', async function () {
|
||||
await signupPage.fillAccountStep({ username: 'user_2', displayName: 'user_2 display name', password: 'superpassword' })
|
||||
await signupPage.validateStep()
|
||||
})
|
||||
|
||||
it('Should validate the third step (channel)', async function () {
|
||||
await signupPage.fillChannelStep({ name: 'user_2_channel' })
|
||||
await signupPage.validateStep()
|
||||
})
|
||||
|
||||
it('Should have a valid end message', async function () {
|
||||
const message = await signupPage.getEndMessage()
|
||||
|
||||
checkEndMessage({
|
||||
message,
|
||||
requiresEmailVerification: false,
|
||||
requiresApproval: true,
|
||||
afterEmailVerification: false
|
||||
})
|
||||
|
||||
await browser.saveScreenshot(getScreenshotPath('request-without-email.png'))
|
||||
})
|
||||
|
||||
it('Should display a message when trying to login with this account', async function () {
|
||||
const error = await loginPage.getLoginError('user_2', 'superpassword')
|
||||
|
||||
expect(error).toContain('awaiting approval')
|
||||
})
|
||||
|
||||
it('Should accept the registration', async function () {
|
||||
await loginPage.loginAsRootUser()
|
||||
|
||||
await adminRegistrationPage.navigateToRegistrationsList()
|
||||
await adminRegistrationPage.accept('user_2', 'moderation response')
|
||||
|
||||
await loginPage.logout()
|
||||
})
|
||||
|
||||
it('Should be able to login with this new account', async function () {
|
||||
await loginPage.login({ username: 'user_2', password: 'superpassword', displayName: 'user_2 display name' })
|
||||
|
||||
await loginPage.logout()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Email verification enabled', function () {
|
||||
const emails: any[] = []
|
||||
|
||||
before(async () => {
|
||||
await MockSMTPServer.Instance.collectEmails(await getEmailPort(), emails)
|
||||
})
|
||||
|
||||
describe('Direct registration', function () {
|
||||
it('Should enable signup without approval', async () => {
|
||||
await prepareSignup({ enabled: true, requiresApproval: false, requiresEmailVerification: true })
|
||||
|
||||
await signupPage.getRegisterMenuButton().waitForDisplayed()
|
||||
})
|
||||
|
||||
it('Should go on signup page', async function () {
|
||||
await signupPage.clickOnRegisterButton()
|
||||
})
|
||||
|
||||
it('Should validate the first step (about page)', async function () {
|
||||
await signupPage.validateStep()
|
||||
})
|
||||
|
||||
it('Should validate the second step (terms)', async function () {
|
||||
await signupPage.checkTerms()
|
||||
await signupPage.validateStep()
|
||||
})
|
||||
|
||||
it('Should validate the third step (account)', async function () {
|
||||
await signupPage.fillAccountStep({ username: 'user_3', displayName: 'user_3 display name', email: 'user_3@example.com' })
|
||||
|
||||
await signupPage.validateStep()
|
||||
})
|
||||
|
||||
it('Should validate the third step (channel)', async function () {
|
||||
await signupPage.fillChannelStep({ name: 'user_3_channel' })
|
||||
|
||||
await signupPage.validateStep()
|
||||
})
|
||||
|
||||
it('Should have a valid end message', async function () {
|
||||
const message = await signupPage.getEndMessage()
|
||||
|
||||
checkEndMessage({
|
||||
message,
|
||||
requiresEmailVerification: true,
|
||||
requiresApproval: false,
|
||||
afterEmailVerification: false
|
||||
})
|
||||
|
||||
await browser.saveScreenshot(getScreenshotPath('direct-with-email.png'))
|
||||
})
|
||||
|
||||
it('Should validate the email', async function () {
|
||||
let email: { text: string }
|
||||
|
||||
while (!(email = findEmailTo(emails, 'user_3@example.com'))) {
|
||||
await browserSleep(100)
|
||||
}
|
||||
|
||||
await go(getVerificationLink(email))
|
||||
|
||||
const message = await signupPage.getEndMessage()
|
||||
|
||||
checkEndMessage({
|
||||
message,
|
||||
requiresEmailVerification: false,
|
||||
requiresApproval: false,
|
||||
afterEmailVerification: true
|
||||
})
|
||||
|
||||
await browser.saveScreenshot(getScreenshotPath('direct-after-email.png'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Registration with approval', function () {
|
||||
it('Should enable signup without approval', async () => {
|
||||
await prepareSignup({ enabled: true, requiresApproval: true, requiresEmailVerification: true })
|
||||
|
||||
await signupPage.getRegisterMenuButton().waitForDisplayed()
|
||||
})
|
||||
|
||||
it('Should go on signup page', async function () {
|
||||
await signupPage.clickOnRegisterButton()
|
||||
})
|
||||
|
||||
it('Should validate the first step (about page)', async function () {
|
||||
await signupPage.validateStep()
|
||||
})
|
||||
|
||||
it('Should validate the second step (terms)', async function () {
|
||||
await signupPage.checkTerms()
|
||||
await signupPage.fillRegistrationReason('my super reason 2')
|
||||
await signupPage.validateStep()
|
||||
})
|
||||
|
||||
it('Should validate the third step (account)', async function () {
|
||||
await signupPage.fillAccountStep({
|
||||
username: 'user_4',
|
||||
displayName: 'user_4 display name',
|
||||
email: 'user_4@example.com',
|
||||
password: 'superpassword'
|
||||
})
|
||||
await signupPage.validateStep()
|
||||
})
|
||||
|
||||
it('Should validate the third step (channel)', async function () {
|
||||
await signupPage.fillChannelStep({ name: 'user_4_channel' })
|
||||
await signupPage.validateStep()
|
||||
})
|
||||
|
||||
it('Should have a valid end message', async function () {
|
||||
const message = await signupPage.getEndMessage()
|
||||
|
||||
checkEndMessage({
|
||||
message,
|
||||
requiresEmailVerification: true,
|
||||
requiresApproval: true,
|
||||
afterEmailVerification: false
|
||||
})
|
||||
|
||||
await browser.saveScreenshot(getScreenshotPath('request-with-email.png'))
|
||||
})
|
||||
|
||||
it('Should display a message when trying to login with this account', async function () {
|
||||
const error = await loginPage.getLoginError('user_4', 'superpassword')
|
||||
|
||||
expect(error).toContain('awaiting approval')
|
||||
})
|
||||
|
||||
it('Should accept the registration', async function () {
|
||||
await loginPage.loginAsRootUser()
|
||||
|
||||
await adminRegistrationPage.navigateToRegistrationsList()
|
||||
await adminRegistrationPage.accept('user_4', 'moderation response 2')
|
||||
|
||||
await loginPage.logout()
|
||||
})
|
||||
|
||||
it('Should validate the email', async function () {
|
||||
let email: { text: string }
|
||||
|
||||
while (!(email = findEmailTo(emails, 'user_4@example.com'))) {
|
||||
await browserSleep(100)
|
||||
}
|
||||
|
||||
await go(getVerificationLink(email))
|
||||
|
||||
const message = await signupPage.getEndMessage()
|
||||
|
||||
checkEndMessage({
|
||||
message,
|
||||
requiresEmailVerification: false,
|
||||
requiresApproval: true,
|
||||
afterEmailVerification: true
|
||||
})
|
||||
|
||||
await browser.saveScreenshot(getScreenshotPath('request-after-email.png'))
|
||||
})
|
||||
})
|
||||
|
||||
after(() => {
|
||||
MockSMTPServer.Instance.kill()
|
||||
})
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await browser.saveScreenshot(getScreenshotPath('after-test.png'))
|
||||
})
|
||||
})
|
||||
76
client/e2e/src/suites-local/user-settings.e2e-spec.ts
Normal file
76
client/e2e/src/suites-local/user-settings.e2e-spec.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { AdminConfigPage } from '../po/admin-config.po'
|
||||
import { LoginPage } from '../po/login.po'
|
||||
import { MyAccountPage } from '../po/my-account.po'
|
||||
import {
|
||||
browserSleep,
|
||||
findEmailTo,
|
||||
getEmailPort,
|
||||
getVerificationLink,
|
||||
go,
|
||||
isMobileDevice,
|
||||
MockSMTPServer,
|
||||
prepareWebBrowser,
|
||||
waitServerUp
|
||||
} from '../utils'
|
||||
|
||||
describe('User settings', () => {
|
||||
let loginPage: LoginPage
|
||||
let myAccountPage: MyAccountPage
|
||||
let adminConfigPage: AdminConfigPage
|
||||
|
||||
const emails: any[] = []
|
||||
|
||||
before(async () => {
|
||||
await waitServerUp()
|
||||
|
||||
loginPage = new LoginPage(isMobileDevice())
|
||||
myAccountPage = new MyAccountPage()
|
||||
adminConfigPage = new AdminConfigPage()
|
||||
|
||||
await MockSMTPServer.Instance.collectEmails(await getEmailPort(), emails)
|
||||
|
||||
await prepareWebBrowser()
|
||||
})
|
||||
|
||||
describe('Email', function () {
|
||||
before(async function () {
|
||||
await loginPage.loginAsRootUser()
|
||||
|
||||
await adminConfigPage.toggleSignup(true)
|
||||
await adminConfigPage.toggleSignupEmailVerification(true)
|
||||
await adminConfigPage.save()
|
||||
|
||||
await browser.refresh()
|
||||
})
|
||||
|
||||
it('Should ask to change the email', async function () {
|
||||
await myAccountPage.navigateToMySettings()
|
||||
await myAccountPage.updateEmail('email2@example.com', loginPage.getRootPassword())
|
||||
|
||||
const pendingEmailBlock = $('.pending-email')
|
||||
await pendingEmailBlock.waitForDisplayed()
|
||||
await expect(pendingEmailBlock).toHaveText(expect.stringContaining('email2@example.com is awaiting email verification'))
|
||||
|
||||
let email: { text: string }
|
||||
|
||||
while (!(email = findEmailTo(emails, 'email2@example.com'))) {
|
||||
await browserSleep(100)
|
||||
}
|
||||
|
||||
await go(getVerificationLink(email))
|
||||
|
||||
const alertBlock = $('.alert-success')
|
||||
await alertBlock.waitForDisplayed()
|
||||
await expect(alertBlock).toHaveText(expect.stringContaining('Email updated'))
|
||||
|
||||
await myAccountPage.navigateToMySettings()
|
||||
const changeEmailBlock = $('.change-email .form-group-description')
|
||||
await changeEmailBlock.waitForDisplayed()
|
||||
await expect(changeEmailBlock).toHaveText(expect.stringContaining('Your current email is email2@example.com'))
|
||||
})
|
||||
})
|
||||
|
||||
after(() => {
|
||||
MockSMTPServer.Instance.kill()
|
||||
})
|
||||
})
|
||||
226
client/e2e/src/suites-local/video-password.e2e-spec.ts
Normal file
226
client/e2e/src/suites-local/video-password.e2e-spec.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
import { LoginPage } from '../po/login.po'
|
||||
import { MyAccountPage } from '../po/my-account.po'
|
||||
import { PlayerPage } from '../po/player.po'
|
||||
import { SignupPage } from '../po/signup.po'
|
||||
import { VideoPublishPage } from '../po/video-publish.po'
|
||||
import { VideoWatchPage } from '../po/video-watch.po'
|
||||
import { go, isMobileDevice, isSafari, prepareWebBrowser, waitServerUp } from '../utils'
|
||||
|
||||
describe('Password protected videos', () => {
|
||||
let videoPublishPage: VideoPublishPage
|
||||
let loginPage: LoginPage
|
||||
let videoWatchPage: VideoWatchPage
|
||||
let signupPage: SignupPage
|
||||
let playerPage: PlayerPage
|
||||
let myAccountPage: MyAccountPage
|
||||
let passwordProtectedVideoUrl: string
|
||||
let playlistUrl: string
|
||||
|
||||
const seed = Math.random()
|
||||
const passwordProtectedVideoName = seed + ' - password protected'
|
||||
const publicVideoName1 = seed + ' - public 1'
|
||||
const publicVideoName2 = seed + ' - public 2'
|
||||
const videoPassword = 'password'
|
||||
const regularUsername = 'user_1'
|
||||
const regularUserPassword = 'user password'
|
||||
const playlistName = seed + ' - playlist'
|
||||
|
||||
function testRateAndComment () {
|
||||
it('Should add and remove like on video', async function () {
|
||||
await videoWatchPage.like()
|
||||
await videoWatchPage.like()
|
||||
})
|
||||
|
||||
it('Should create thread on video', async function () {
|
||||
await videoWatchPage.createThread('My first comment')
|
||||
})
|
||||
|
||||
it('Should reply to thread on video', async function () {
|
||||
await videoWatchPage.createReply('My first reply')
|
||||
})
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
await waitServerUp()
|
||||
|
||||
loginPage = new LoginPage(isMobileDevice())
|
||||
videoPublishPage = new VideoPublishPage()
|
||||
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
|
||||
signupPage = new SignupPage()
|
||||
playerPage = new PlayerPage()
|
||||
myAccountPage = new MyAccountPage()
|
||||
|
||||
await prepareWebBrowser()
|
||||
})
|
||||
|
||||
describe('Owner', function () {
|
||||
before(async () => {
|
||||
await loginPage.loginAsRootUser()
|
||||
})
|
||||
|
||||
it('Should login, upload a public video and save it to a playlist', async () => {
|
||||
await videoPublishPage.navigateTo()
|
||||
await videoPublishPage.uploadVideo('video.mp4')
|
||||
await videoPublishPage.validSecondStep(publicVideoName1)
|
||||
|
||||
await videoPublishPage.clickOnWatch()
|
||||
await videoWatchPage.waitWatchVideoName(publicVideoName1)
|
||||
|
||||
await videoWatchPage.clickOnSave()
|
||||
|
||||
await videoWatchPage.createPlaylist(playlistName)
|
||||
|
||||
await videoWatchPage.saveToPlaylist(playlistName)
|
||||
await browser.pause(5000)
|
||||
})
|
||||
|
||||
it('Should upload a password protected video', async () => {
|
||||
await videoPublishPage.navigateTo()
|
||||
await videoPublishPage.uploadVideo('video2.mp4')
|
||||
await videoPublishPage.setAsPasswordProtected(videoPassword)
|
||||
await videoPublishPage.validSecondStep(passwordProtectedVideoName)
|
||||
|
||||
await videoPublishPage.clickOnWatch()
|
||||
await videoWatchPage.waitWatchVideoName(passwordProtectedVideoName)
|
||||
|
||||
passwordProtectedVideoUrl = await browser.getUrl()
|
||||
})
|
||||
|
||||
it('Should save to playlist the password protected video', async () => {
|
||||
await videoWatchPage.clickOnSave()
|
||||
await videoWatchPage.saveToPlaylist(playlistName)
|
||||
})
|
||||
|
||||
it('Should upload a second public video and save it to playlist', async () => {
|
||||
await videoPublishPage.navigateTo()
|
||||
|
||||
await videoPublishPage.uploadVideo('video3.mp4')
|
||||
await videoPublishPage.validSecondStep(publicVideoName2)
|
||||
await videoPublishPage.clickOnWatch()
|
||||
|
||||
await videoWatchPage.waitWatchVideoName(publicVideoName2)
|
||||
await videoWatchPage.clickOnSave()
|
||||
await videoWatchPage.saveToPlaylist(playlistName)
|
||||
})
|
||||
|
||||
it('Should play video without password', async function () {
|
||||
await go(passwordProtectedVideoUrl)
|
||||
|
||||
await videoWatchPage.waitWatchVideoName(passwordProtectedVideoName)
|
||||
|
||||
expect(await videoWatchPage.getPrivacy()).toBe('Password protected')
|
||||
await playerPage.playAndPauseVideo(false, 2)
|
||||
})
|
||||
|
||||
testRateAndComment()
|
||||
|
||||
it('Should play video on embed without password', async function () {
|
||||
await videoWatchPage.goOnAssociatedEmbed()
|
||||
await playerPage.playAndPauseVideo(false, 2)
|
||||
})
|
||||
|
||||
it('Should have the playlist in my account', async function () {
|
||||
await go('/')
|
||||
await myAccountPage.navigateToMyPlaylists()
|
||||
const videosNumberText = await myAccountPage.getPlaylistVideosText(playlistName)
|
||||
|
||||
expect(videosNumberText).toEqual('3 videos')
|
||||
await myAccountPage.clickOnPlaylist(playlistName)
|
||||
|
||||
const count = await myAccountPage.countTotalPlaylistElements()
|
||||
expect(count).toEqual(3)
|
||||
})
|
||||
|
||||
it('Should update the playlist to public', async () => {
|
||||
const url = await browser.getUrl()
|
||||
const regex = /\/my-library\/video-playlists\/([^/]+)/i
|
||||
const match = url.match(regex)
|
||||
const uuid = match ? match[1] : null
|
||||
|
||||
expect(uuid).not.toBeNull()
|
||||
|
||||
await myAccountPage.updatePlaylistPrivacy(uuid, 'Public')
|
||||
})
|
||||
|
||||
it('Should watch the playlist', async () => {
|
||||
await myAccountPage.clickOnPlaylist(playlistName)
|
||||
await myAccountPage.playPlaylist()
|
||||
|
||||
await videoWatchPage.waitWatchVideoName(publicVideoName1, 40 * 1000)
|
||||
playlistUrl = await browser.getUrl()
|
||||
|
||||
await videoWatchPage.waitWatchVideoName(passwordProtectedVideoName, 40 * 1000)
|
||||
await videoWatchPage.waitWatchVideoName(publicVideoName2, 40 * 1000)
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await loginPage.logout()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Regular users', function () {
|
||||
before(async () => {
|
||||
await signupPage.fullSignup({
|
||||
accountInfo: {
|
||||
username: regularUsername,
|
||||
password: regularUserPassword
|
||||
},
|
||||
channelInfo: {
|
||||
name: 'user_1_channel'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('Should requires password to play video', async function () {
|
||||
await go(passwordProtectedVideoUrl)
|
||||
|
||||
await videoWatchPage.fillVideoPassword(videoPassword)
|
||||
await videoWatchPage.waitWatchVideoName(passwordProtectedVideoName)
|
||||
|
||||
expect(await videoWatchPage.getPrivacy()).toBe('Password protected')
|
||||
await playerPage.playAndPauseVideo(true, 2)
|
||||
})
|
||||
|
||||
testRateAndComment()
|
||||
|
||||
it('Should requires password to play video on embed', async function () {
|
||||
await videoWatchPage.goOnAssociatedEmbed(true)
|
||||
await playerPage.fillEmbedVideoPassword(videoPassword)
|
||||
await playerPage.playAndPauseVideo(false, 2)
|
||||
})
|
||||
|
||||
it('Should watch the playlist without password protected video', async () => {
|
||||
await go(playlistUrl)
|
||||
await playerPage.playVideo()
|
||||
await videoWatchPage.waitWatchVideoName(publicVideoName2, 40 * 1000)
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await loginPage.logout()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Anonymous users', function () {
|
||||
it('Should requires password to play video', async function () {
|
||||
await go(passwordProtectedVideoUrl)
|
||||
|
||||
await videoWatchPage.fillVideoPassword(videoPassword)
|
||||
await videoWatchPage.waitWatchVideoName(passwordProtectedVideoName)
|
||||
|
||||
expect(await videoWatchPage.getPrivacy()).toBe('Password protected')
|
||||
await playerPage.playAndPauseVideo(true, 2)
|
||||
})
|
||||
|
||||
it('Should requires password to play video on embed', async function () {
|
||||
await videoWatchPage.goOnAssociatedEmbed(true)
|
||||
await playerPage.fillEmbedVideoPassword(videoPassword)
|
||||
await playerPage.playAndPauseVideo(false, 2)
|
||||
})
|
||||
|
||||
it('Should watch the playlist without password protected video', async () => {
|
||||
await go(playlistUrl)
|
||||
await playerPage.playVideo()
|
||||
await videoWatchPage.waitWatchVideoName(publicVideoName2, 40 * 1000)
|
||||
})
|
||||
})
|
||||
})
|
||||
9
client/e2e/src/types/wdio.d.ts
vendored
Normal file
9
client/e2e/src/types/wdio.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
declare global {
|
||||
namespace WebdriverIO {
|
||||
interface Element {
|
||||
chooseFile: (path: string) => Promise<void>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
67
client/e2e/src/utils/common.ts
Normal file
67
client/e2e/src/utils/common.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
export async function browserSleep (amount: number) {
|
||||
await browser.pause(amount)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function isMobileDevice () {
|
||||
const platformName = (browser.capabilities['platformName'] || '').toLowerCase()
|
||||
|
||||
return platformName === 'android' || platformName === 'ios'
|
||||
}
|
||||
|
||||
export function isAndroid () {
|
||||
const platformName = (browser.capabilities['platformName'] || '').toLowerCase()
|
||||
|
||||
return platformName === 'android'
|
||||
}
|
||||
|
||||
export function isSafari () {
|
||||
return browser.capabilities['browserName'] &&
|
||||
browser.capabilities['browserName'].toLowerCase() === 'safari'
|
||||
}
|
||||
|
||||
export function isIOS () {
|
||||
return isMobileDevice() && isSafari()
|
||||
}
|
||||
|
||||
export async function go (url: string) {
|
||||
await browser.url(url)
|
||||
|
||||
await browser.execute(() => {
|
||||
const style = document.createElement('style')
|
||||
style.innerHTML = 'p-toast { display: none }'
|
||||
document.head.appendChild(style)
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function prepareWebBrowser (options: {
|
||||
hidePrivacyConcerns?: boolean // default true
|
||||
} = {}) {
|
||||
const { hidePrivacyConcerns = true } = options
|
||||
|
||||
if (hidePrivacyConcerns) {
|
||||
try {
|
||||
await browser.execute(() => {
|
||||
localStorage.setItem('video-watch-privacy-concern', 'true')
|
||||
})
|
||||
} catch {
|
||||
console.log('Cannot set local storage to hide privacy concerns')
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMobileDevice() && process.env.MOZ_HEADLESS_WIDTH) {
|
||||
await browser.setWindowSize(+process.env.MOZ_HEADLESS_WIDTH, +process.env.MOZ_HEADLESS_HEIGHT)
|
||||
}
|
||||
}
|
||||
|
||||
export async function waitServerUp () {
|
||||
await browser.waitUntil(async () => {
|
||||
await go('/')
|
||||
await browserSleep(500)
|
||||
|
||||
return $('<my-app>').isDisplayed()
|
||||
}, { timeout: 20 * 1000 })
|
||||
}
|
||||
76
client/e2e/src/utils/elements.ts
Normal file
76
client/e2e/src/utils/elements.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
export async function getCheckbox (name: string) {
|
||||
const input = $(`my-peertube-checkbox input[id=${name}]`)
|
||||
await input.waitForExist()
|
||||
|
||||
return input.parentElement()
|
||||
}
|
||||
|
||||
export function isCheckboxSelected (name: string) {
|
||||
return $(`input[id=${name}]`).isSelected()
|
||||
}
|
||||
|
||||
export async function setCheckboxEnabled (name: string, enabled: boolean) {
|
||||
if (await isCheckboxSelected(name) === enabled) return
|
||||
|
||||
const checkbox = await getCheckbox(name)
|
||||
|
||||
await checkbox.scrollIntoView({ block: 'center' })
|
||||
await checkbox.waitForClickable()
|
||||
await checkbox.click()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function isRadioSelected (name: string) {
|
||||
await $(`input[id=${name}] + label`).waitForClickable()
|
||||
|
||||
return $(`input[id=${name}]`).isSelected()
|
||||
}
|
||||
|
||||
export async function clickOnRadio (name: string) {
|
||||
const label = $(`input[id=${name}] + label`)
|
||||
|
||||
await label.waitForClickable()
|
||||
await label.click()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function selectCustomSelect (id: string, valueLabel: string) {
|
||||
const wrapper = $(`[formcontrolname=${id}] span[role=combobox]`)
|
||||
|
||||
await wrapper.waitForExist()
|
||||
await wrapper.scrollIntoView({ block: 'center' })
|
||||
await wrapper.waitForClickable()
|
||||
await wrapper.click()
|
||||
|
||||
const getOption = async () => {
|
||||
const options = await $$(`[formcontrolname=${id}] li[role=option]`).filter(async o => {
|
||||
const text = await o.getText()
|
||||
|
||||
return text.trimStart().startsWith(valueLabel)
|
||||
})
|
||||
|
||||
if (options.length === 0) return undefined
|
||||
|
||||
return options[0]
|
||||
}
|
||||
|
||||
await browser.waitUntil(async () => {
|
||||
const option = await getOption()
|
||||
if (!option) return false
|
||||
|
||||
return option.isDisplayed()
|
||||
})
|
||||
|
||||
return (await getOption()).click()
|
||||
}
|
||||
|
||||
export async function findParentElement (
|
||||
el: ChainablePromiseElement,
|
||||
finder: (el: ChainablePromiseElement) => Promise<boolean>
|
||||
) {
|
||||
if (await finder(el) === true) return el
|
||||
|
||||
return findParentElement(el.parentElement(), finder)
|
||||
}
|
||||
38
client/e2e/src/utils/email.ts
Normal file
38
client/e2e/src/utils/email.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export function getVerificationLink (email: { text: string }) {
|
||||
const { text } = email
|
||||
|
||||
const regexp = /\[(?<link>http:\/\/[^\]]+)\]/g
|
||||
const matched = text.matchAll(regexp)
|
||||
|
||||
if (!matched) throw new Error('Could not find verification link in email')
|
||||
|
||||
for (const match of matched) {
|
||||
const link = match.groups.link
|
||||
|
||||
if (link.includes('/verify-account/')) {
|
||||
return link.replace('127.0.0.1', 'localhost')
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Could not find /verify-account/ link')
|
||||
}
|
||||
|
||||
export function findEmailTo (emails: { text: string, to: { address: string }[] }[], to: string) {
|
||||
for (const email of emails) {
|
||||
for (const { address } of email.to) {
|
||||
if (address === to) return email
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export async function getEmailPort () {
|
||||
const key = browser.options.baseUrl + '-emailPort'
|
||||
// FIXME: typings are wrong, get returns a promise
|
||||
// FIXME: use * because the key is not properly escaped by the shared store when using get(key)
|
||||
const emailPort = (await (browser.sharedStore.get('*') as unknown as Promise<number>))[key]
|
||||
if (!emailPort) throw new Error('Invalid email port')
|
||||
|
||||
return emailPort
|
||||
}
|
||||
13
client/e2e/src/utils/files.ts
Normal file
13
client/e2e/src/utils/files.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { mkdir, rm } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const SCREENSHOTS_DIRECTORY = 'screenshots'
|
||||
|
||||
export async function createScreenshotsDirectory () {
|
||||
await rm(SCREENSHOTS_DIRECTORY, { recursive: true, force: true })
|
||||
await mkdir(SCREENSHOTS_DIRECTORY, { recursive: true })
|
||||
}
|
||||
|
||||
export function getScreenshotPath (filename: string) {
|
||||
return join(SCREENSHOTS_DIRECTORY, filename)
|
||||
}
|
||||
113
client/e2e/src/utils/hooks.ts
Normal file
113
client/e2e/src/utils/hooks.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { setValue } from '@wdio/shared-store-service'
|
||||
import { ChildProcessWithoutNullStreams } from 'node:child_process'
|
||||
import { basename } from 'node:path'
|
||||
import { createScreenshotsDirectory, getScreenshotPath } from './files'
|
||||
import { runCommand, runServer } from './server'
|
||||
|
||||
let appInstance: number
|
||||
let app: ChildProcessWithoutNullStreams
|
||||
|
||||
let emailPort: number
|
||||
|
||||
async function beforeLocalSuite (suite: any) {
|
||||
const config = buildConfig(suite.file)
|
||||
|
||||
await runCommand('npm run clean:server:test -- ' + appInstance)
|
||||
app = runServer(appInstance, config)
|
||||
}
|
||||
|
||||
function afterLocalSuite () {
|
||||
app.kill()
|
||||
app = undefined
|
||||
}
|
||||
|
||||
async function afterLocalTest (test: { file: string }) {
|
||||
const filename = basename(test.file).replace(/\.ts$/, '')
|
||||
|
||||
await browser.saveScreenshot(getScreenshotPath(`${filename}-after-test.png`))
|
||||
}
|
||||
|
||||
async function beforeLocalSession (config: { baseUrl: string }, capabilities: { browserName: string }) {
|
||||
createScreenshotsDirectory()
|
||||
|
||||
appInstance = capabilities['browserName'] === 'chrome'
|
||||
? 1
|
||||
: 2
|
||||
|
||||
emailPort = 1025 + appInstance
|
||||
|
||||
config.baseUrl = 'http://localhost:900' + appInstance
|
||||
|
||||
await setValue(config.baseUrl + '-emailPort', emailPort)
|
||||
}
|
||||
|
||||
async function onBrowserStackPrepare () {
|
||||
const appInstance = 1
|
||||
|
||||
await runCommand('npm run clean:server:test -- ' + appInstance)
|
||||
app = runServer(appInstance)
|
||||
}
|
||||
|
||||
function onBrowserStackComplete () {
|
||||
app.kill()
|
||||
app = undefined
|
||||
}
|
||||
|
||||
export {
|
||||
afterLocalSuite,
|
||||
afterLocalTest,
|
||||
beforeLocalSession,
|
||||
beforeLocalSuite,
|
||||
onBrowserStackComplete,
|
||||
onBrowserStackPrepare
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function buildConfig (suiteFile: string = undefined) {
|
||||
const filename = basename(suiteFile)
|
||||
|
||||
if (filename === 'custom-server-defaults.e2e-spec.ts') {
|
||||
return {
|
||||
defaults: {
|
||||
publish: {
|
||||
download_enabled: false,
|
||||
comments_policy: 2,
|
||||
privacy: 2,
|
||||
licence: 4
|
||||
},
|
||||
p2p: {
|
||||
webapp: {
|
||||
enabled: false
|
||||
},
|
||||
embed: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filename === 'signup.e2e-spec.ts' || filename === 'user-settings.e2e-spec.ts') {
|
||||
return {
|
||||
signup: {
|
||||
limit: -1
|
||||
},
|
||||
smtp: {
|
||||
hostname: '127.0.0.1',
|
||||
port: emailPort
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filename === 'video-password.e2e-spec.ts') {
|
||||
return {
|
||||
signup: {
|
||||
enabled: true,
|
||||
limit: -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
8
client/e2e/src/utils/index.ts
Normal file
8
client/e2e/src/utils/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export * from './common'
|
||||
export * from './elements'
|
||||
export * from './email'
|
||||
export * from './files'
|
||||
export * from './hooks'
|
||||
export * from './mock-smtp'
|
||||
export * from './server'
|
||||
export * from './urls'
|
||||
57
client/e2e/src/utils/mock-smtp.ts
Normal file
57
client/e2e/src/utils/mock-smtp.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import MailDev from '@peertube/maildev'
|
||||
|
||||
class MockSMTPServer {
|
||||
|
||||
private static instance: MockSMTPServer
|
||||
private started = false
|
||||
private maildev: any
|
||||
private emails: object[]
|
||||
|
||||
collectEmails (port: number, emailsCollection: object[]) {
|
||||
return new Promise<number>((res, rej) => {
|
||||
this.emails = emailsCollection
|
||||
|
||||
if (this.started) {
|
||||
return res(undefined)
|
||||
}
|
||||
|
||||
this.maildev = new MailDev({
|
||||
ip: '127.0.0.1',
|
||||
smtp: port,
|
||||
disableWeb: true,
|
||||
silent: true
|
||||
})
|
||||
|
||||
this.maildev.on('new', email => {
|
||||
this.emails.push(email)
|
||||
})
|
||||
|
||||
this.maildev.listen(err => {
|
||||
if (err) return rej(err)
|
||||
|
||||
this.started = true
|
||||
|
||||
return res(port)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
kill () {
|
||||
if (!this.maildev) return
|
||||
|
||||
this.maildev.close()
|
||||
|
||||
this.maildev = null
|
||||
MockSMTPServer.instance = null
|
||||
}
|
||||
|
||||
static get Instance () {
|
||||
return this.instance || (this.instance = new this())
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
MockSMTPServer
|
||||
}
|
||||
68
client/e2e/src/utils/server.ts
Normal file
68
client/e2e/src/utils/server.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { exec, spawn } from 'node:child_process'
|
||||
import { join, resolve } from 'node:path'
|
||||
|
||||
function runServer (appInstance: number, config: any = {}) {
|
||||
const env = Object.create(process.env)
|
||||
|
||||
env['NODE_OPTIONS'] = ''
|
||||
env['NODE_ENV'] = 'test'
|
||||
env['NODE_APP_INSTANCE'] = appInstance + ''
|
||||
|
||||
env['NODE_CONFIG'] = JSON.stringify({
|
||||
rates_limit: {
|
||||
api: {
|
||||
max: 5000
|
||||
},
|
||||
login: {
|
||||
max: 5000
|
||||
}
|
||||
},
|
||||
log: {
|
||||
level: 'warn'
|
||||
},
|
||||
transcoding: {
|
||||
enabled: false
|
||||
},
|
||||
video_studio: {
|
||||
enabled: false
|
||||
},
|
||||
|
||||
...config
|
||||
})
|
||||
|
||||
const forkOptions = {
|
||||
env,
|
||||
cwd: getRootCWD(),
|
||||
detached: false
|
||||
}
|
||||
|
||||
const p = spawn('node', [ join('dist', 'server.js') ], forkOptions)
|
||||
p.stderr.on('data', data => console.error(data.toString()))
|
||||
p.stdout.on('data', data => console.error(data.toString()))
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
function runCommand (command: string) {
|
||||
return new Promise<void>((res, rej) => {
|
||||
// Reset NODE_OPTIONS env set by webdriverio
|
||||
const env = { ...process.env, NODE_OPTIONS: '' }
|
||||
|
||||
const p = exec(command, { env, cwd: getRootCWD() })
|
||||
|
||||
p.stderr.on('data', data => console.error(data.toString()))
|
||||
p.on('error', err => rej(err))
|
||||
p.on('exit', () => res())
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
runServer,
|
||||
runCommand
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function getRootCWD () {
|
||||
return resolve('../..')
|
||||
}
|
||||
23
client/e2e/src/utils/urls.ts
Normal file
23
client/e2e/src/utils/urls.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
const FIXTURE_URLS = {
|
||||
INTERNAL_WEB_VIDEO: 'https://peertube2.cpy.re/w/pwfz7NizSdPD4mJcbbmNwa?mode=web-video&start=0',
|
||||
INTERNAL_HLS_VIDEO: 'https://peertube2.cpy.re/w/pwfz7NizSdPD4mJcbbmNwa?start=0',
|
||||
|
||||
INTERNAL_EMBED_WEB_VIDEO: 'https://peertube2.cpy.re/videos/embed/pwfz7NizSdPD4mJcbbmNwa?mode=web-video&start=0',
|
||||
INTERNAL_EMBED_HLS_VIDEO: 'https://peertube2.cpy.re/videos/embed/pwfz7NizSdPD4mJcbbmNwa?start=0',
|
||||
|
||||
INTERNAL_HLS_ONLY_VIDEO: 'https://peertube2.cpy.re/w/tKQmHcqdYZRdCszLUiWM3V?start=0',
|
||||
INTERNAL_EMBED_HLS_ONLY_VIDEO: 'https://peertube2.cpy.re/videos/embed/tKQmHcqdYZRdCszLUiWM3V?start=0',
|
||||
|
||||
WEB_VIDEO: 'https://peertube2.cpy.re/w/122d093a-1ede-43bd-bd34-59d2931ffc5e',
|
||||
|
||||
HLS_EMBED: 'https://peertube2.cpy.re/videos/embed/969bf103-7818-43b5-94a0-de159e13de50',
|
||||
HLS_PLAYLIST_EMBED: 'https://peertube2.cpy.re/video-playlists/embed/73804a40-da9a-40c2-b1eb-2c6d9eec8f0a',
|
||||
|
||||
LIVE_VIDEO: 'https://peertube2.cpy.re/w/oBw6LwsMWWRkmXYfuYRpJd',
|
||||
|
||||
IMPORT_URL: 'https://download.cpy.re/peertube/good_video.mp4'
|
||||
}
|
||||
|
||||
export {
|
||||
FIXTURE_URLS
|
||||
}
|
||||
28
client/e2e/tsconfig.json
Normal file
28
client/e2e/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/app",
|
||||
"noImplicitAny": false,
|
||||
"esModuleInterop": true,
|
||||
"module": "commonjs",
|
||||
"target": "ES2018",
|
||||
"typeRoots": [
|
||||
"../node_modules/@wdio",
|
||||
"../node_modules/@types",
|
||||
"../node_modules"
|
||||
],
|
||||
"types": [
|
||||
"node",
|
||||
"@wdio/globals/types",
|
||||
"@wdio/mocha-framework",
|
||||
"expect-webdriverio"
|
||||
]
|
||||
},
|
||||
"ts-node": {
|
||||
"files": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"./*.ts"
|
||||
]
|
||||
}
|
||||
179
client/e2e/wdio.browserstack.conf.ts
Normal file
179
client/e2e/wdio.browserstack.conf.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import { onBrowserStackComplete, onBrowserStackPrepare } from './src/utils'
|
||||
import { config as mainConfig } from './wdio.main.conf'
|
||||
|
||||
const user = process.env.BROWSERSTACK_USER
|
||||
const key = process.env.BROWSERSTACK_KEY
|
||||
|
||||
if (!user) throw new Error('Miss browser stack user')
|
||||
if (!key) throw new Error('Miss browser stack key')
|
||||
|
||||
function buildMainOptions (sessionName: string) {
|
||||
return {
|
||||
projectName: 'PeerTube',
|
||||
buildName: 'Main E2E - ' + new Date().toISOString(),
|
||||
sessionName,
|
||||
consoleLogs: 'info',
|
||||
networkLogs: true
|
||||
}
|
||||
}
|
||||
|
||||
function buildBStackDesktopOptions (options: {
|
||||
sessionName: string
|
||||
resolution: string
|
||||
os?: string
|
||||
osVersion?: string
|
||||
}) {
|
||||
const { sessionName, resolution, os, osVersion } = options
|
||||
|
||||
return {
|
||||
'bstack:options': {
|
||||
...buildMainOptions(sessionName),
|
||||
|
||||
os,
|
||||
osVersion,
|
||||
resolution
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildBStackMobileOptions (options: {
|
||||
sessionName: string
|
||||
deviceName: string
|
||||
osVersion: string
|
||||
}) {
|
||||
const { sessionName, deviceName, osVersion } = options
|
||||
|
||||
return {
|
||||
'bstack:options': {
|
||||
...buildMainOptions(sessionName),
|
||||
|
||||
realMobile: true,
|
||||
osVersion,
|
||||
deviceName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
config: {
|
||||
...mainConfig,
|
||||
|
||||
user,
|
||||
key,
|
||||
|
||||
maxInstances: 5,
|
||||
|
||||
capabilities: [
|
||||
{
|
||||
browserName: 'Chrome',
|
||||
|
||||
...buildBStackDesktopOptions({ sessionName: 'Latest Chrome Desktop', resolution: '1280x1024', os: 'Windows', osVersion: '8' })
|
||||
},
|
||||
{
|
||||
browserName: 'Firefox',
|
||||
browserVersion: '79', // Oldest supported version
|
||||
|
||||
...buildBStackDesktopOptions({ sessionName: 'Firefox ESR Desktop', resolution: '1280x1024', os: 'Windows', osVersion: '8' })
|
||||
},
|
||||
{
|
||||
browserName: 'Safari',
|
||||
browserVersion: '14',
|
||||
|
||||
...buildBStackDesktopOptions({ sessionName: 'Safari Desktop', resolution: '1280x1024' })
|
||||
},
|
||||
{
|
||||
browserName: 'Firefox',
|
||||
|
||||
...buildBStackDesktopOptions({ sessionName: 'Firefox Latest', resolution: '1280x1024', os: 'Windows', osVersion: '8' })
|
||||
},
|
||||
{
|
||||
browserName: 'Edge',
|
||||
|
||||
...buildBStackDesktopOptions({ sessionName: 'Edge Latest', resolution: '1280x1024' })
|
||||
},
|
||||
|
||||
{
|
||||
browserName: 'Chrome',
|
||||
|
||||
...buildBStackMobileOptions({ sessionName: 'Latest Chrome Android', deviceName: 'Samsung Galaxy S10', osVersion: '9.0' })
|
||||
},
|
||||
{
|
||||
browserName: 'Safari',
|
||||
|
||||
...buildBStackMobileOptions({ sessionName: 'Safari iPhone', deviceName: 'iPhone 12', osVersion: '14' })
|
||||
},
|
||||
|
||||
{
|
||||
browserName: 'Safari',
|
||||
|
||||
...buildBStackMobileOptions({ sessionName: 'Safari iPad', deviceName: 'iPad Pro 12.9 2021', osVersion: '14' })
|
||||
}
|
||||
],
|
||||
|
||||
host: 'hub-cloud.browserstack.com',
|
||||
connectionRetryTimeout: 240000,
|
||||
waitforTimeout: 20000,
|
||||
|
||||
specs: [
|
||||
// We don't want to test "local" tests
|
||||
'./src/suites-all/*.e2e-spec.ts'
|
||||
],
|
||||
|
||||
services: [
|
||||
[
|
||||
'browserstack',
|
||||
{ browserstackLocal: true }
|
||||
]
|
||||
],
|
||||
|
||||
onWorkerStart: function (_cid, capabilities) {
|
||||
if (capabilities['bstack:options'].realMobile === true) {
|
||||
capabilities['bstack:options'].local = false
|
||||
}
|
||||
},
|
||||
|
||||
before: function () {
|
||||
require('./src/commands/upload')
|
||||
|
||||
// Force keep alive: https://www.browserstack.com/docs/automate/selenium/error-codes/keep-alive-not-used#Node_JS
|
||||
const http = require('node:http')
|
||||
const https = require('node:https')
|
||||
|
||||
const keepAliveTimeout = 30 * 1000
|
||||
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (http.globalAgent?.hasOwnProperty('keepAlive')) {
|
||||
http.globalAgent.keepAlive = true
|
||||
https.globalAgent.keepAlive = true
|
||||
http.globalAgent.keepAliveMsecs = keepAliveTimeout
|
||||
https.globalAgent.keepAliveMsecs = keepAliveTimeout
|
||||
} else {
|
||||
const agent = new http.Agent({
|
||||
keepAlive: true,
|
||||
keepAliveMsecs: keepAliveTimeout
|
||||
})
|
||||
|
||||
const secureAgent = new https.Agent({
|
||||
keepAlive: true,
|
||||
keepAliveMsecs: keepAliveTimeout
|
||||
})
|
||||
|
||||
const httpRequest = http.request
|
||||
const httpsRequest = https.request
|
||||
|
||||
http.request = function (options, callback) {
|
||||
if (options.protocol === 'https:') {
|
||||
options['agent'] = secureAgent
|
||||
return httpsRequest(options, callback)
|
||||
} else {
|
||||
options['agent'] = agent
|
||||
return httpRequest(options, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onPrepare: onBrowserStackPrepare,
|
||||
onComplete: onBrowserStackComplete
|
||||
} as WebdriverIO.Config
|
||||
}
|
||||
53
client/e2e/wdio.local-test.conf.ts
Normal file
53
client/e2e/wdio.local-test.conf.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { afterLocalSuite, afterLocalTest, beforeLocalSession, beforeLocalSuite } from './src/utils'
|
||||
import { config as mainConfig } from './wdio.main.conf'
|
||||
|
||||
const prefs = {
|
||||
'intl.accept_languages': 'en'
|
||||
}
|
||||
|
||||
// Chrome headless does not support prefs
|
||||
process.env.LANG = 'en'
|
||||
|
||||
// https://github.com/mozilla/geckodriver/issues/1354#issuecomment-479456411
|
||||
process.env.MOZ_HEADLESS_WIDTH = '1280'
|
||||
process.env.MOZ_HEADLESS_HEIGHT = '1024'
|
||||
|
||||
const windowSizeArg = `--window-size=${process.env.MOZ_HEADLESS_WIDTH},${process.env.MOZ_HEADLESS_HEIGHT}`
|
||||
|
||||
module.exports = {
|
||||
config: {
|
||||
...mainConfig,
|
||||
|
||||
runner: 'local',
|
||||
|
||||
maxInstances: 1,
|
||||
specFileRetries: 0,
|
||||
|
||||
capabilities: [
|
||||
{
|
||||
'browserName': 'chrome',
|
||||
'acceptInsecureCerts': true,
|
||||
'goog:chromeOptions': {
|
||||
args: [ '--disable-gpu', windowSizeArg ],
|
||||
prefs
|
||||
}
|
||||
}
|
||||
// {
|
||||
// 'browserName': 'firefox',
|
||||
// 'moz:firefoxOptions': {
|
||||
// binary: '/usr/bin/firefox-developer-edition',
|
||||
// args: [ '--headless', windowSizeArg ],
|
||||
|
||||
// prefs
|
||||
// }
|
||||
// }
|
||||
],
|
||||
|
||||
services: [ 'shared-store' ],
|
||||
|
||||
beforeSession: beforeLocalSession,
|
||||
beforeSuite: beforeLocalSuite,
|
||||
afterSuite: afterLocalSuite,
|
||||
afterTest: afterLocalTest
|
||||
} as WebdriverIO.Config
|
||||
}
|
||||
48
client/e2e/wdio.local.conf.ts
Normal file
48
client/e2e/wdio.local.conf.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { afterLocalSuite, afterLocalTest, beforeLocalSession, beforeLocalSuite } from './src/utils'
|
||||
import { config as mainConfig } from './wdio.main.conf'
|
||||
|
||||
const prefs = { 'intl.accept_languages': 'en' }
|
||||
process.env.LANG = 'en'
|
||||
|
||||
// https://github.com/mozilla/geckodriver/issues/1354#issuecomment-479456411
|
||||
process.env.MOZ_HEADLESS_WIDTH = '1280'
|
||||
process.env.MOZ_HEADLESS_HEIGHT = '1024'
|
||||
|
||||
const windowSizeArg = `--window-size=${process.env.MOZ_HEADLESS_WIDTH},${process.env.MOZ_HEADLESS_HEIGHT}`
|
||||
|
||||
module.exports = {
|
||||
config: {
|
||||
...mainConfig,
|
||||
|
||||
runner: 'local',
|
||||
|
||||
maxInstancesPerCapability: 1,
|
||||
|
||||
capabilities: [
|
||||
{
|
||||
'browserName': 'chrome',
|
||||
'goog:chromeOptions': {
|
||||
binary: '/usr/bin/google-chrome-stable',
|
||||
args: [ '--headless', '--disable-gpu', windowSizeArg ],
|
||||
prefs
|
||||
}
|
||||
},
|
||||
{
|
||||
'browserName': 'firefox',
|
||||
'moz:firefoxOptions': {
|
||||
binary: '/usr/bin/firefox-developer-edition',
|
||||
args: [ '--headless', windowSizeArg ],
|
||||
|
||||
prefs
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
services: [ 'shared-store' ],
|
||||
|
||||
beforeSession: beforeLocalSession,
|
||||
beforeSuite: beforeLocalSuite,
|
||||
afterSuite: afterLocalSuite,
|
||||
afterTest: afterLocalTest
|
||||
} as WebdriverIO.Config
|
||||
}
|
||||
110
client/e2e/wdio.main.conf.ts
Normal file
110
client/e2e/wdio.main.conf.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
export const config = {
|
||||
//
|
||||
// ====================
|
||||
// Runner Configuration
|
||||
// ====================
|
||||
//
|
||||
//
|
||||
// ==================
|
||||
// Specify Test Files
|
||||
// ==================
|
||||
// Define which test specs should run. The pattern is relative to the directory
|
||||
// from which `wdio` was called.
|
||||
//
|
||||
// The specs are defined as an array of spec files (optionally using wildcards
|
||||
// that will be expanded). The test for each spec file will be run in a separate
|
||||
// worker process. In order to have a group of spec files run in the same worker
|
||||
// process simply enclose them in an array within the specs array.
|
||||
//
|
||||
// If you are calling `wdio` from an NPM script (see https://docs.npmjs.com/cli/run-script),
|
||||
// then the current working directory is where your `package.json` resides, so `wdio`
|
||||
// will be called from there.
|
||||
//
|
||||
specs: [
|
||||
'./src/suites-all/*.e2e-spec.ts',
|
||||
'./src/suites-local/*.e2e-spec.ts'
|
||||
],
|
||||
// Patterns to exclude.
|
||||
exclude: [
|
||||
// 'path/to/excluded/files'
|
||||
],
|
||||
//
|
||||
// ===================
|
||||
// Test Configurations
|
||||
// ===================
|
||||
// Define all options that are relevant for the WebdriverIO instance here
|
||||
//
|
||||
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
||||
logLevel: 'info',
|
||||
//
|
||||
// Set specific log levels per logger
|
||||
// loggers:
|
||||
// - webdriver, webdriverio
|
||||
// - @wdio/browserstack-service, @wdio/devtools-service, @wdio/sauce-service
|
||||
// - @wdio/mocha-framework, @wdio/jasmine-framework
|
||||
// - @wdio/local-runner
|
||||
// - @wdio/sumologic-reporter
|
||||
// - @wdio/cli, @wdio/config, @wdio/utils
|
||||
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
||||
// logLevels: {
|
||||
// webdriver: 'info',
|
||||
// '@wdio/appium-service': 'info'
|
||||
// },
|
||||
//
|
||||
// If you only want to run your tests until a specific amount of tests have failed use
|
||||
// bail (default is 0 - don't bail, run all tests).
|
||||
bail: 0,
|
||||
//
|
||||
// Set a base URL in order to shorten url command calls. If your `url` parameter starts
|
||||
// with `/`, the base url gets prepended, not including the path portion of your baseUrl.
|
||||
// If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url
|
||||
// gets prepended directly.
|
||||
baseUrl: 'http://127.0.0.1:9001',
|
||||
//
|
||||
// Default timeout for all waitFor* commands.
|
||||
waitforTimeout: 5000,
|
||||
//
|
||||
// Default timeout in milliseconds for request
|
||||
// if browser driver or grid doesn't send response
|
||||
connectionRetryTimeout: 120000,
|
||||
//
|
||||
// Default request retries count
|
||||
connectionRetryCount: 3,
|
||||
|
||||
// Framework you want to run your specs with.
|
||||
// The following are supported: Mocha, Jasmine, and Cucumber
|
||||
// see also: https://webdriver.io/docs/frameworks
|
||||
//
|
||||
// Make sure you have the wdio adapter package for the specific framework installed
|
||||
// before running any tests.
|
||||
framework: 'mocha',
|
||||
//
|
||||
// The number of times to retry the entire specfile when it fails as a whole
|
||||
specFileRetries: 2,
|
||||
//
|
||||
// Delay in seconds between the spec file retry attempts
|
||||
// specFileRetriesDelay: 0,
|
||||
//
|
||||
// Whether or not retried specfiles should be retried immediately or deferred to the end of the queue
|
||||
// specFileRetriesDeferred: false,
|
||||
//
|
||||
// Test reporter for stdout.
|
||||
// The only one supported by default is 'dot'
|
||||
// see also: https://webdriver.io/docs/dot-reporter
|
||||
reporters: [ 'spec' ],
|
||||
|
||||
//
|
||||
// Options to be passed to Mocha.
|
||||
// See the full list at http://mochajs.org/
|
||||
mochaOpts: {
|
||||
ui: 'bdd',
|
||||
timeout: 60000,
|
||||
bail: true
|
||||
},
|
||||
|
||||
tsConfigPath: require('node:path').join(__dirname, './tsconfig.json'),
|
||||
|
||||
before: function () {
|
||||
require('./src/commands/upload')
|
||||
}
|
||||
} as Partial<WebdriverIO.Config>
|
||||
Reference in New Issue
Block a user