Migrate to webdriverio

pull/4387/head
Chocobozzz 2021-08-30 16:24:25 +02:00
parent 2a4c9669d2
commit 3419e0e1fe
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
25 changed files with 2399 additions and 1173 deletions

View File

@ -22,6 +22,8 @@
"plugin:@angular-eslint/template/process-inline-templates" "plugin:@angular-eslint/template/process-inline-templates"
], ],
"rules": { "rules": {
"jsdoc/newline-after-description": "off",
"jsdoc/check-alignment": "off",
"lines-between-class-members": "off", "lines-between-class-members": "off",
"@typescript-eslint/lines-between-class-members": [ "off" ], "@typescript-eslint/lines-between-class-members": [ "off" ],
"arrow-body-style": "off", "arrow-body-style": "off",

1
client/.gitignore vendored
View File

@ -8,3 +8,4 @@
/src/locale/target/iso639_*.xml /src/locale/target/iso639_*.xml
/src/locale/target/player_*.xml /src/locale/target/player_*.xml
/src/locale/target/server_*.xml /src/locale/target/server_*.xml
/e2e/local.log

View File

@ -233,21 +233,6 @@
"with": "src/environments/environment.hmr.ts" "with": "src/environments/environment.hmr.ts"
} }
] ]
},
"e2e": {
"localize": false,
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "6kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.e2e.ts"
}
]
} }
} }
}, },
@ -266,10 +251,6 @@
}, },
"ar-locale": { "ar-locale": {
"browserTarget": "PeerTube:build:ar-locale" "browserTarget": "PeerTube:build:ar-locale"
},
"e2e": {
"browserTarget": "PeerTube:build:e2e",
"proxyConfig": "e2e/proxy.config.json"
} }
} }
}, },
@ -361,25 +342,6 @@
} }
} }
} }
},
"PeerTube-e2e": {
"root": "e2e/",
"sourceRoot": "",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "PeerTube:serve:e2e"
},
"configurations": {
"local": {
"protractorConfig": "e2e/local-protractor.conf.js"
}
}
}
}
} }
}, },
"defaultProject": "PeerTube", "defaultProject": "PeerTube",

View File

@ -1,56 +0,0 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const {SpecReporter} = require('jasmine-spec-reporter')
exports.config = {
allScriptsTimeout: 25000,
specs: ['./src/**/*.e2e-spec.ts'],
directConnect: true,
multiCapabilities: [
{
'browserName': 'firefox',
'name': 'Firefox',
'moz:firefoxOptions': {
binary: '/usr/bin/firefox-developer-edition',
// args: ["-headless"],
log: {
"level": "info" // default is "info"
}
}
},
{
'browserName': 'firefox',
'name': 'Firefox ESR',
'moz:firefoxOptions': {
binary: '/usr/bin/firefox-esr',
// args: ["-headless"],
log: {
"level": "info" // default is "info"
}
}
},
{
'browserName': 'chrome',
'name': 'Chromium'
}
],
maxSessions: 1,
baseUrl: 'http://localhost:3000/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 45000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
})
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }))
}
}

View File

@ -1,96 +0,0 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const {SpecReporter} = require('jasmine-spec-reporter')
exports.config = {
allScriptsTimeout: 25000,
specs: [ './src/**/*.e2e-spec.ts' ],
seleniumAddress: 'http://hub-cloud.browserstack.com/wd/hub',
commonCapabilities: {
'browserstack.user': process.env.BROWSERSTACK_USER,
'browserstack.key': process.env.BROWSERSTACK_KEY,
'browserstack.local': true,
'browserstack.console': 'verbose',
'browserstack.networkLogs': true,
'browserstack.debug': true,
project: 'PeerTube',
build: 'Main',
name: 'Bstack-[Protractor] Parallel Test'
},
multiCapabilities: [
{
browserName: 'Safari',
version: '11.1',
name: 'Safari Desktop',
resolution: '1280x1024'
},
{
browserName: 'Chrome',
name: 'Latest Chrome Desktop',
resolution: '1280x1024'
},
{
browserName: 'Firefox',
version: '68', // ESR
name: 'Firefox ESR Desktop',
resolution: '1280x1024'
},
{
browserName: 'Firefox',
name: 'Latest Firefox Desktop',
resolution: '1280x1024'
},
{
browserName: 'Edge',
name: 'Latest Edge Desktop',
resolution: '1280x1024'
},
{
browserName: 'Chrome',
device: 'Google Nexus 6',
real_mobile: 'true',
os_version: '5.0',
name: 'Latest Chrome Android'
},
{
browserName: 'Safari',
device: 'iPhone 8 Plus',
real_mobile: 'true',
os_version: '11',
name: 'Safari iPhone'
},
{
browserName: 'Safari',
device: 'iPad 7th',
real_mobile: 'true',
os_version: '13',
name: 'Safari iPad'
}
],
// maxSessions: 1,
// BrowserStack compatible ports: https://www.browserstack.com/question/664
baseUrl: 'http://localhost:3333/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 45000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
})
jasmine.getEnv().addReporter(new SpecReporter({
spec: { displayStacktrace: 'raw' }
}))
}
}
exports.config.multiCapabilities.forEach(function (caps) {
for (var i in exports.config.commonCapabilities) caps[i] = caps[i] || exports.config.commonCapabilities[i]
})

View File

@ -1,37 +0,0 @@
{
"/api": {
"target": "http://localhost:9000",
"secure": false
},
"/plugins": {
"target": "http://localhost:9000",
"secure": false
},
"/themes": {
"target": "http://localhost:9000",
"secure": false
},
"/static": {
"target": "http://localhost:9000",
"secure": false
},
"/lazy-static": {
"target": "http://localhost:9000",
"secure": false
},
"/socket.io": {
"target": "ws://localhost:9000",
"secure": false,
"ws": true
},
"/!(client)**": {
"target": "http://localhost:3333/client/index.html",
"secure": false,
"logLevel": "debug"
},
"/!(client)**/**": {
"target": "http://localhost:3333/client/index.html",
"secure": false,
"logLevel": "debug"
}
}

View 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)

View File

@ -1,23 +1,25 @@
import { browser, element, by } from 'protractor' import { go } from '../utils'
export class LoginPage { export class LoginPage {
async loginAsRootUser () { async loginAsRootUser () {
await browser.get('/login') await go('/login')
await browser.executeScript(`window.localStorage.setItem('no_instance_config_warning_modal', 'true')`) await browser.execute(`window.localStorage.setItem('no_instance_config_warning_modal', 'true')`)
await browser.executeScript(`window.localStorage.setItem('no_welcome_modal', 'true')`) await browser.execute(`window.localStorage.setItem('no_welcome_modal', 'true')`)
element(by.css('input#username')).sendKeys('root') await $('input#username').setValue('root')
element(by.css('input#password')).sendKeys('test1') await $('input#password').setValue('test1')
await browser.sleep(1000) await browser.pause(1000)
await element(by.css('form input[type=submit]')).click() await $('form input[type=submit]').click()
expect(this.getLoggedInInfo().getText()).toContain('root') await this.getLoggedInInfoElem().waitForExist()
await expect(this.getLoggedInInfoElem()).toHaveText('root')
} }
private getLoggedInInfo () { private getLoggedInInfoElem () {
return element(by.css('.logged-in-display-name')) return $('.logged-in-display-name')
} }
} }

View File

@ -1,85 +1,117 @@
import { by, element, browser } from 'protractor' import { go } from '../utils'
export class MyAccountPage { export class MyAccountPage {
navigateToMyVideos () { navigateToMyVideos () {
return element(by.css('a[href="/my-library/videos"]')).click() return $('a[href="/my-library/videos"]').click()
} }
navigateToMyPlaylists () { navigateToMyPlaylists () {
return element(by.css('a[href="/my-library/video-playlists"]')).click() return $('a[href="/my-library/video-playlists"]').click()
} }
navigateToMyHistory () { navigateToMyHistory () {
return element(by.css('a[href="/my-library/history/videos"]')).click() return $('a[href="/my-library/history/videos"]').click()
} }
// My account Videos // My account Videos
async removeVideo (name: string) { async removeVideo (name: string) {
const container = this.getVideoElement(name) const container = await this.getVideoElement(name)
await container.element(by.css('.dropdown-toggle')).click() await container.$('.dropdown-toggle').click()
const dropdownMenu = container.element(by.css('.dropdown-menu .dropdown-item:nth-child(2)')) const dropdownMenu = () => container.$('.dropdown-menu .dropdown-item:nth-child(2)')
await browser.wait(browser.ExpectedConditions.presenceOf(dropdownMenu))
return dropdownMenu.click() await dropdownMenu().waitForDisplayed()
return dropdownMenu().click()
} }
validRemove () { validRemove () {
return element(by.css('input[type=submit]')).click() return $('input[type=submit]').click()
} }
countVideos (names: string[]) { async countVideos (names: string[]) {
return element.all(by.css('.video')) const elements = await $$('.video').filter(async e => {
.filter(e => { const t = await e.$('.video-miniature-name').getText()
return e.element(by.css('.video-miniature-name'))
.getText() return names.some(n => t.includes(n))
.then(t => names.some(n => t.includes(n))) })
})
.count() return elements.length
} }
// My account playlists // My account playlists
getPlaylistVideosText (name: string) { async getPlaylistVideosText (name: string) {
return this.getPlaylist(name).element(by.css('.miniature-playlist-info-overlay')).getText() const elem = await this.getPlaylist(name)
return elem.$('.miniature-playlist-info-overlay').getText()
} }
clickOnPlaylist (name: string) { async clickOnPlaylist (name: string) {
return this.getPlaylist(name).element(by.css('.miniature-thumbnail')).click() const elem = await this.getPlaylist(name)
return elem.$('.miniature-thumbnail').click()
} }
countTotalPlaylistElements () { async countTotalPlaylistElements () {
return element.all(by.css('my-video-playlist-element-miniature')).count() await $('<my-video-playlist-element-miniature>').waitForDisplayed()
return $$('<my-video-playlist-element-miniature>').length
} }
playPlaylist () { playPlaylist () {
return element(by.css('.playlist-info .miniature-thumbnail')).click() return $('.playlist-info .miniature-thumbnail').click()
} }
async goOnAssociatedPlaylistEmbed () { async goOnAssociatedPlaylistEmbed () {
let url = await browser.getCurrentUrl() let url = await browser.getUrl()
url = url.replace('/w/p/', '/video-playlists/embed/') url = url.replace('/w/p/', '/video-playlists/embed/')
url = url.replace(':3333', ':9001') url = url.replace(':3333', ':9001')
return browser.get(url) return go(url)
} }
// My account Videos // My account Videos
private getVideoElement (name: string) { private async getVideoElement (name: string) {
return element.all(by.css('.video')) const video = async () => {
.filter(e => e.element(by.css('.video-miniature-name')).getText().then(t => t.includes(name))) const videos = await $$('.video').filter(async e => {
.first() const t = await e.$('.video-miniature-name').getText()
return t.includes(name)
})
return videos[0]
}
await browser.waitUntil(async () => {
return (await video()).isDisplayed()
})
return video()
} }
// My account playlists // My account playlists
private getPlaylist (name: string) { private async getPlaylist (name: string) {
return element.all(by.css('my-video-playlist-miniature')) const playlist = () => {
.filter(e => e.element(by.css('.miniature-name')).getText().then(t => t.includes(name))) return $$('my-video-playlist-miniature')
.first() .filter(async e => {
const t = await e.$('.miniature-name').getText()
return t.includes(name)
})
.then(elems => elems[0])
}
await browser.waitUntil(async () => {
const el = await playlist()
return el?.isDisplayed()
})
return playlist()
} }
} }

View File

@ -1,45 +1,54 @@
import { browser, by, element } from 'protractor'
import { browserSleep, isIOS, isMobileDevice, isSafari } from '../utils' import { browserSleep, isIOS, isMobileDevice, isSafari } from '../utils'
export class PlayerPage { export class PlayerPage {
async getWatchVideoPlayerCurrentTime () { getWatchVideoPlayerCurrentTime () {
const elem = element(by.css('video')) const elem = $('video')
return elem.getAttribute('currentTime') if (isIOS()) {
return elem.getAttribute('currentTime')
.then(t => parseInt(t, 10))
.then(t => Math.round(t))
}
return elem.getProperty('currentTime')
} }
waitUntilPlaylistInfo (text: string) { waitUntilPlaylistInfo (text: string, maxTime: number) {
const elem = element(by.css('.video-js .vjs-playlist-info')) return browser.waitUntil(async () => {
return (await $('.video-js .vjs-playlist-info').getText()).includes(text)
return browser.wait(browser.ExpectedConditions.textToBePresentInElement(elem, text)) }, { timeout: maxTime })
} }
waitUntilPlayerWrapper () { waitUntilPlayerWrapper () {
const elem = element(by.css('#placeholder-preview')) return browser.waitUntil(async () => {
return !!(await $('#placeholder-preview'))
return browser.wait(browser.ExpectedConditions.presenceOf(elem)) })
} }
async playAndPauseVideo (isAutoplay: boolean) { async playAndPauseVideo (isAutoplay: boolean) {
const videojsEl = element(by.css('div.video-js')) const videojsElem = () => $('div.video-js')
await browser.wait(browser.ExpectedConditions.elementToBeClickable(videojsEl))
await videojsElem().waitForExist()
// Autoplay is disabled on iOS and Safari // Autoplay is disabled on iOS and Safari
if (await isIOS() || await isSafari() || await isMobileDevice()) { if (isIOS() || isSafari() || isMobileDevice()) {
// We can't play the video using protractor if it is not muted // We can't play the video using protractor if it is not muted
await browser.executeScript(`document.querySelector('video').muted = true`) await browser.execute(`document.querySelector('video').muted = true`)
await this.clickOnPlayButton() await this.clickOnPlayButton()
} else if (isAutoplay === false) { } else if (isAutoplay === false) {
await this.clickOnPlayButton() await this.clickOnPlayButton()
} }
await browserSleep(2000) await browserSleep(2000)
await browser.wait(browser.ExpectedConditions.invisibilityOf(element(by.css('.vjs-loading-spinner'))))
await browserSleep(2000) await browser.waitUntil(async () => {
return !await $('.vjs-loading-spinner').isDisplayedInViewport()
}, { timeout: 20 * 1000 })
await videojsEl.click() await browserSleep(4000)
await videojsElem().click()
} }
async playVideo () { async playVideo () {
@ -47,8 +56,9 @@ export class PlayerPage {
} }
private async clickOnPlayButton () { private async clickOnPlayButton () {
const playButton = element(by.css('.vjs-big-play-button')) const playButton = () => $('.vjs-big-play-button')
await browser.wait(browser.ExpectedConditions.elementToBeClickable(playButton))
await playButton.click() await playButton().waitForClickable()
await playButton().click()
} }
} }

View File

@ -1,11 +1,11 @@
import { by, element } from 'protractor'
export class VideoUpdatePage { export class VideoUpdatePage {
async updateName (videoName: string) { async updateName (videoName: string) {
const nameInput = element(by.css('input#name')) const nameInput = $('input#name')
await nameInput.clear()
await nameInput.sendKeys(videoName) await nameInput.waitForDisplayed()
await nameInput.clearValue()
await nameInput.setValue(videoName)
} }
async validUpdate () { async validUpdate () {
@ -15,6 +15,6 @@ export class VideoUpdatePage {
} }
private getSubmitButton () { private getSubmitButton () {
return element(by.css('.submit-container .action-button')) return $('.submit-container .action-button')
} }
} }

View File

@ -1,33 +1,29 @@
import { browser, by, element } from 'protractor'
import { FileDetector } from 'selenium-webdriver/remote'
import { join } from 'path' import { join } from 'path'
export class VideoUploadPage { export class VideoUploadPage {
async navigateTo () { async navigateTo () {
await element(by.css('.header .publish-button')).click() await $('.header .publish-button').click()
return browser.wait(browser.ExpectedConditions.visibilityOf(element(by.css('.upload-video-container')))) await $('.upload-video-container').waitForDisplayed()
} }
async uploadVideo () { async uploadVideo () {
browser.setFileDetector(new FileDetector())
const fileToUpload = join(__dirname, '../../fixtures/video.mp4') const fileToUpload = join(__dirname, '../../fixtures/video.mp4')
const fileInputSelector = '.upload-video-container input[type=file]' const fileInputSelector = '.upload-video-container input[type=file]'
const parentFileInput = '.upload-video-container .button-file' const parentFileInput = '.upload-video-container .button-file'
// Avoid sending keys on non visible element // Avoid sending keys on non visible element
await browser.executeScript(`document.querySelector('${fileInputSelector}').style.opacity = 1`) await browser.execute(`document.querySelector('${fileInputSelector}').style.opacity = 1`)
await browser.executeScript(`document.querySelector('${parentFileInput}').style.overflow = 'initial'`) await browser.execute(`document.querySelector('${parentFileInput}').style.overflow = 'initial'`)
await browser.sleep(1000) await browser.pause(1000)
const elem = element(by.css(fileInputSelector)) const elem = await $(fileInputSelector)
await elem.sendKeys(fileToUpload) await elem.chooseFile(fileToUpload)
// Wait for the upload to finish // Wait for the upload to finish
await browser.wait(async () => { await browser.waitUntil(async () => {
const actionButton = this.getSecondStepSubmitButton().element(by.css('.action-button')) const actionButton = this.getSecondStepSubmitButton().$('.action-button')
const klass = await actionButton.getAttribute('class') const klass = await actionButton.getAttribute('class')
return !klass.includes('disabled') return !klass.includes('disabled')
@ -35,16 +31,18 @@ export class VideoUploadPage {
} }
async validSecondUploadStep (videoName: string) { async validSecondUploadStep (videoName: string) {
const nameInput = element(by.css('input#name')) const nameInput = $('input#name')
await nameInput.clear() await nameInput.clearValue()
await nameInput.sendKeys(videoName) await nameInput.setValue(videoName)
await this.getSecondStepSubmitButton().click() await this.getSecondStepSubmitButton().click()
return browser.wait(browser.ExpectedConditions.urlContains('/w/')) return browser.waitUntil(async () => {
return (await browser.getUrl()).includes('/w/')
})
} }
private getSecondStepSubmitButton () { private getSecondStepSubmitButton () {
return element(by.css('.submit-container my-button')) return $('.submit-container my-button')
} }
} }

View File

@ -1,5 +1,4 @@
import { browser, by, element, ElementFinder, ExpectedConditions } from 'protractor' import { browserSleep, go } from '../utils'
import { browserSleep, isMobileDevice } from '../utils'
export class VideoWatchPage { export class VideoWatchPage {
async goOnVideosList (isMobileDevice: boolean, isSafari: boolean) { async goOnVideosList (isMobileDevice: boolean, isSafari: boolean) {
@ -12,19 +11,19 @@ export class VideoWatchPage {
url = '/videos/recently-added' url = '/videos/recently-added'
} }
await browser.get(url, 20000) await go(url)
// Waiting the following element does not work on Safari... // Waiting the following element does not work on Safari...
if (isSafari) return browserSleep(3000) if (isSafari) return browserSleep(3000)
const elem = element.all(by.css('.videos .video-miniature .video-miniature-name')).first() await $('.videos .video-miniature .video-miniature-name').waitForDisplayed()
return browser.wait(browser.ExpectedConditions.visibilityOf(elem))
} }
getVideosListName () { async getVideosListName () {
return element.all(by.css('.videos .video-miniature .video-miniature-name')) const elems = await $$('.videos .video-miniature .video-miniature-name')
.getText() const texts = await Promise.all(elems.map(e => e.getText()))
.then((texts: any) => texts.map((t: any) => t.trim()))
return texts.map(t => t.trim())
} }
waitWatchVideoName (videoName: string, isMobileDevice: boolean, isSafari: boolean) { waitWatchVideoName (videoName: string, isMobileDevice: boolean, isSafari: boolean) {
@ -33,99 +32,134 @@ export class VideoWatchPage {
// On mobile we display the first node, on desktop the second // On mobile we display the first node, on desktop the second
const index = isMobileDevice ? 0 : 1 const index = isMobileDevice ? 0 : 1
const elem = element.all(by.css('.video-info .video-info-name')).get(index) return browser.waitUntil(async () => {
return browser.wait(browser.ExpectedConditions.textToBePresentInElement(elem, videoName)) return (await $$('.video-info .video-info-name')[index].getText()).includes(videoName)
})
} }
getVideoName () { getVideoName () {
return this.getVideoNameElement().getText() return this.getVideoNameElement().then(e => e.getText())
} }
async goOnAssociatedEmbed () { async goOnAssociatedEmbed () {
let url = await browser.getCurrentUrl() let url = await browser.getUrl()
url = url.replace('/w/', '/videos/embed/') url = url.replace('/w/', '/videos/embed/')
url = url.replace(':3333', ':9001') url = url.replace(':3333', ':9001')
return browser.get(url) return go(url)
} }
async goOnP2PMediaLoaderEmbed () { goOnP2PMediaLoaderEmbed () {
return browser.get('https://peertube2.cpy.re/videos/embed/969bf103-7818-43b5-94a0-de159e13de50') return go(
'https://peertube2.cpy.re/videos/embed/969bf103-7818-43b5-94a0-de159e13de50'
)
} }
async goOnP2PMediaLoaderPlaylistEmbed () { goOnP2PMediaLoaderPlaylistEmbed () {
return browser.get('https://peertube2.cpy.re/video-playlists/embed/73804a40-da9a-40c2-b1eb-2c6d9eec8f0a') return go(
'https://peertube2.cpy.re/video-playlists/embed/73804a40-da9a-40c2-b1eb-2c6d9eec8f0a'
)
} }
async clickOnVideo (videoName: string) { async clickOnVideo (videoName: string) {
const video = element.all(by.css('.videos .video-miniature .video-miniature-name')) const video = async () => {
.filter(e => e.getText().then(t => t === videoName )) const videos = await $$('.videos .video-miniature .video-miniature-name').filter(async e => {
.first() const t = await e.getText()
await browser.wait(browser.ExpectedConditions.elementToBeClickable(video)) return t === videoName
await video.click() })
await browser.wait(browser.ExpectedConditions.urlContains('/w/')) 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 () { async clickOnFirstVideo () {
const video = element.all(by.css('.videos .video-miniature .video-thumbnail')).first() const video = () => $('.videos .video-miniature .video-thumbnail')
const videoName = element.all(by.css('.videos .video-miniature .video-miniature-name')).first() const videoName = () => $('.videos .video-miniature .video-miniature-name')
// Don't know why but the expectation fails on Safari await video().waitForClickable()
await browser.wait(browser.ExpectedConditions.elementToBeClickable(video))
const textToReturn = videoName.getText() const textToReturn = await videoName().getText()
await video.click() await video().click()
await browser.waitUntil(async () => (await browser.getUrl()).includes('/w/'))
await browser.wait(browser.ExpectedConditions.urlContains('/w/'))
return textToReturn return textToReturn
} }
async clickOnUpdate () { async clickOnUpdate () {
const dropdown = element(by.css('my-video-actions-dropdown .action-button')) const dropdown = $('my-video-actions-dropdown .action-button')
await dropdown.click() await dropdown.click()
const items: ElementFinder[] = await element.all(by.css('.dropdown-menu.show .dropdown-item')) await $('.dropdown-menu.show .dropdown-item').waitForDisplayed()
const items = await $$('.dropdown-menu.show .dropdown-item')
for (const item of items) { for (const item of items) {
const href = await item.getAttribute('href') const href = await item.getAttribute('href')
if (href && href.includes('/update/')) { if (href?.includes('/update/')) {
await item.click() await item.click()
return return
} }
} }
} }
async clickOnSave () { clickOnSave () {
return element(by.css('.action-button-save')).click() return $('.action-button-save').click()
} }
async createPlaylist (name: string) { async createPlaylist (name: string) {
await element(by.css('.new-playlist-button')).click() const newPlaylistButton = () => $('.new-playlist-button')
await element(by.css('#displayName')).sendKeys(name) await newPlaylistButton().waitForClickable()
await newPlaylistButton().click()
return element(by.css('.new-playlist-block input[type=submit]')).click() const displayName = () => $('#displayName')
await displayName().waitForDisplayed()
await displayName().setValue(name)
return $('.new-playlist-block input[type=submit]').click()
} }
async saveToPlaylist (name: string) { async saveToPlaylist (name: string) {
return element.all(by.css('my-video-add-to-playlist .playlist')) const playlist = () => $('my-video-add-to-playlist').$(`.playlist=${name}`)
.filter(p => p.getText().then(t => t === name))
.click() await playlist().waitForDisplayed()
return playlist().click()
} }
waitUntilVideoName (name: string, maxTime: number) { waitUntilVideoName (name: string, maxTime: number) {
const elem = this.getVideoNameElement() return browser.waitUntil(async () => {
return (await this.getVideoName()) === name
return browser.wait(ExpectedConditions.textToBePresentInElement(elem, name), maxTime) }, { timeout: maxTime })
} }
private getVideoNameElement () { private async getVideoNameElement () {
// We have 2 video info name block, pick the first that is not empty // We have 2 video info name block, pick the first that is not empty
return element.all(by.css('.video-info-first-row .video-info-name')) const elem = async () => {
.filter(e => e.getText().then(t => !!t)) const elems = await $$('.video-info-first-row .video-info-name').filter(e => e.isDisplayed())
.first()
return elems[0]
}
await browser.waitUntil(async () => {
const e = await elem()
return e?.isDisplayed()
})
return elem()
} }
} }

5
client/e2e/src/types/wdio.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare namespace WebdriverIO {
interface Element {
chooseFile: (path: string) => Promise<void>
}
}

View File

@ -1,34 +1,37 @@
import { browser } from 'protractor'
async function browserSleep (amount: number) { async function browserSleep (amount: number) {
const oldValue = await browser.waitForAngularEnabled() await browser.pause(amount)
// iOS does not seem to work with protractor
// https://github.com/angular/protractor/issues/2840
if (await isIOS()) browser.waitForAngularEnabled(true)
await browser.sleep(amount)
if (await isIOS()) browser.waitForAngularEnabled(oldValue)
} }
async function isMobileDevice () { function isMobileDevice () {
const caps = await browser.getCapabilities() const platformName = (browser.capabilities['platformName'] || '').toLowerCase()
return caps.get('realMobile') === 'true' || caps.get('realMobile') === true
return platformName === 'android' || platformName === 'ios'
} }
async function isSafari () { function isSafari () {
const caps = await browser.getCapabilities() return browser.capabilities['browserName'] &&
return caps.get('browserName') && caps.get('browserName').toLowerCase() === 'safari' browser.capabilities['browserName'].toLowerCase() === 'safari'
} }
async function isIOS () { function isIOS () {
return await isMobileDevice() && await isSafari() return isMobileDevice() && isSafari()
} }
export { async function go (url: string) {
await browser.url(url)
// Hide notifications that could fail tests when hiding buttons
await browser.execute(() => {
const style = document.createElement('style')
style.innerHTML = 'p-toast { display: none }'
document.head.appendChild(style)
})
}
export {
isMobileDevice, isMobileDevice,
isSafari, isSafari,
isIOS, isIOS,
go,
browserSleep browserSleep
} }

View File

@ -1,14 +1,13 @@
import { browser } from 'protractor'
import { LoginPage } from './po/login.po' import { LoginPage } from './po/login.po'
import { MyAccountPage } from './po/my-account' import { MyAccountPage } from './po/my-account'
import { PlayerPage } from './po/player.po' import { PlayerPage } from './po/player.po'
import { VideoUpdatePage } from './po/video-update.po' import { VideoUpdatePage } from './po/video-update.po'
import { VideoUploadPage } from './po/video-upload.po' import { VideoUploadPage } from './po/video-upload.po'
import { VideoWatchPage } from './po/video-watch.po' import { VideoWatchPage } from './po/video-watch.po'
import { isIOS, isMobileDevice, isSafari } from './utils' import { browserSleep, go, isIOS, isMobileDevice, isSafari } from './utils'
async function skipIfUploadNotSupported () { function isUploadUnsupported () {
if (await isMobileDevice() || await isSafari()) { if (isMobileDevice() || isSafari()) {
console.log('Skipping because we are on a real device or Safari and BrowserStack does not support file upload.') console.log('Skipping because we are on a real device or Safari and BrowserStack does not support file upload.')
return true return true
} }
@ -24,11 +23,30 @@ describe('Videos workflow', () => {
let loginPage: LoginPage let loginPage: LoginPage
let playerPage: PlayerPage let playerPage: PlayerPage
let videoName = new Date().getTime() + ' video' let videoName = Math.random() + ' video'
const video2Name = new Date().getTime() + ' second video' const video2Name = Math.random() + ' second video'
const playlistName = new Date().getTime() + ' playlist' const playlistName = Math.random() + ' playlist'
let videoWatchUrl: string 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 browser.waitUntil(async () => {
await go('/')
await browserSleep(500)
return $('<my-app>').isDisplayed()
}, { timeout: 20 * 1000 })
})
beforeEach(async () => { beforeEach(async () => {
videoWatchPage = new VideoWatchPage() videoWatchPage = new VideoWatchPage()
videoUploadPage = new VideoUploadPage() videoUploadPage = new VideoUploadPage()
@ -37,25 +55,13 @@ describe('Videos workflow', () => {
loginPage = new LoginPage() loginPage = new LoginPage()
playerPage = new PlayerPage() playerPage = new PlayerPage()
if (await isIOS()) { if (!isMobileDevice()) {
// iOS does not seem to work with protractor await browser.maximizeWindow()
// https://github.com/angular/protractor/issues/2840
browser.waitForAngularEnabled(false)
console.log('iOS detected')
} else if (await isMobileDevice()) {
console.log('Android detected.')
} else if (await isSafari()) {
console.log('Safari detected.')
}
if (!await isMobileDevice()) {
await browser.driver.manage().window().maximize()
} }
}) })
it('Should log in', async () => { it('Should log in', async () => {
if (await isMobileDevice() || await isSafari()) { if (isMobileDevice() || isSafari()) {
console.log('Skipping because we are on a real device or Safari and BrowserStack does not support file upload.') console.log('Skipping because we are on a real device or Safari and BrowserStack does not support file upload.')
return return
} }
@ -64,7 +70,7 @@ describe('Videos workflow', () => {
}) })
it('Should upload a video', async () => { it('Should upload a video', async () => {
if (await skipIfUploadNotSupported()) return if (isUploadUnsupported()) return
await videoUploadPage.navigateTo() await videoUploadPage.navigateTo()
@ -73,62 +79,52 @@ describe('Videos workflow', () => {
}) })
it('Should list videos', async () => { it('Should list videos', async () => {
await videoWatchPage.goOnVideosList(await isMobileDevice(), await isSafari()) await videoWatchPage.goOnVideosList(isMobileDevice(), isSafari())
if (await skipIfUploadNotSupported()) return if (isUploadUnsupported()) return
const videoNames = videoWatchPage.getVideosListName() const videoNames = await videoWatchPage.getVideosListName()
expect(videoNames).toContain(videoName) expect(videoNames).toContain(videoName)
}) })
it('Should go on video watch page', async () => { it('Should go on video watch page', async () => {
let videoNameToExcept = videoName let videoNameToExcept = videoName
if (await isMobileDevice() || await isSafari()) { if (isMobileDevice() || isSafari()) {
await browser.get('https://peertube2.cpy.re/w/122d093a-1ede-43bd-bd34-59d2931ffc5e') await go('https://peertube2.cpy.re/w/122d093a-1ede-43bd-bd34-59d2931ffc5e')
videoNameToExcept = 'E2E tests' videoNameToExcept = 'E2E tests'
} else { } else {
await videoWatchPage.clickOnVideo(videoName) await videoWatchPage.clickOnVideo(videoName)
} }
return videoWatchPage.waitWatchVideoName(videoNameToExcept, await isMobileDevice(), await isSafari()) return videoWatchPage.waitWatchVideoName(videoNameToExcept, isMobileDevice(), isSafari())
}) })
it('Should play the video', async () => { it('Should play the video', async () => {
videoWatchUrl = await browser.getCurrentUrl() videoWatchUrl = await browser.getUrl()
await playerPage.playAndPauseVideo(true) await playerPage.playAndPauseVideo(true)
expect(playerPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(2) expect(await playerPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(2)
}) })
it('Should watch the associated embed video', async () => { it('Should watch the associated embed video', async () => {
const oldValue = await browser.waitForAngularEnabled()
await browser.waitForAngularEnabled(false)
await videoWatchPage.goOnAssociatedEmbed() await videoWatchPage.goOnAssociatedEmbed()
await playerPage.playAndPauseVideo(false) await playerPage.playAndPauseVideo(false)
expect(playerPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(2) expect(await playerPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(2)
await browser.waitForAngularEnabled(oldValue)
}) })
it('Should watch the p2p media loader embed video', async () => { it('Should watch the p2p media loader embed video', async () => {
const oldValue = await browser.waitForAngularEnabled()
await browser.waitForAngularEnabled(false)
await videoWatchPage.goOnP2PMediaLoaderEmbed() await videoWatchPage.goOnP2PMediaLoaderEmbed()
await playerPage.playAndPauseVideo(false) await playerPage.playAndPauseVideo(false)
expect(playerPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(2) expect(await playerPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(2)
await browser.waitForAngularEnabled(oldValue)
}) })
it('Should update the video', async () => { it('Should update the video', async () => {
if (await skipIfUploadNotSupported()) return if (isUploadUnsupported()) return
await browser.get(videoWatchUrl) await go(videoWatchUrl)
await videoWatchPage.clickOnUpdate() await videoWatchPage.clickOnUpdate()
@ -142,14 +138,14 @@ describe('Videos workflow', () => {
}) })
it('Should add the video in my playlist', async () => { it('Should add the video in my playlist', async () => {
if (await skipIfUploadNotSupported()) return if (isUploadUnsupported()) return
await videoWatchPage.clickOnSave() await videoWatchPage.clickOnSave()
await videoWatchPage.createPlaylist(playlistName) await videoWatchPage.createPlaylist(playlistName)
await videoWatchPage.saveToPlaylist(playlistName) await videoWatchPage.saveToPlaylist(playlistName)
await browser.sleep(5000) await browser.pause(5000)
await videoUploadPage.navigateTo() await videoUploadPage.navigateTo()
@ -161,7 +157,7 @@ describe('Videos workflow', () => {
}) })
it('Should have the playlist in my account', async () => { it('Should have the playlist in my account', async () => {
if (await skipIfUploadNotSupported()) return if (isUploadUnsupported()) return
await myAccountPage.navigateToMyPlaylists() await myAccountPage.navigateToMyPlaylists()
@ -175,26 +171,18 @@ describe('Videos workflow', () => {
}) })
it('Should watch the playlist', async () => { it('Should watch the playlist', async () => {
if (await skipIfUploadNotSupported()) return if (isUploadUnsupported()) return
await myAccountPage.playPlaylist() await myAccountPage.playPlaylist()
const oldValue = await browser.waitForAngularEnabled() await videoWatchPage.waitUntilVideoName(video2Name, 30 * 1000)
await browser.waitForAngularEnabled(false)
await videoWatchPage.waitUntilVideoName(video2Name, 20000 * 1000)
await browser.waitForAngularEnabled(oldValue)
}) })
it('Should watch the webtorrent playlist in the embed', async () => { it('Should watch the webtorrent playlist in the embed', async () => {
if (await skipIfUploadNotSupported()) return if (isUploadUnsupported()) return
const accessToken = await browser.executeScript(`return window.localStorage.getItem('access_token');`) const accessToken = await browser.execute(`return window.localStorage.getItem('access_token');`)
const refreshToken = await browser.executeScript(`return window.localStorage.getItem('refresh_token');`) const refreshToken = await browser.execute(`return window.localStorage.getItem('refresh_token');`)
const oldValue = await browser.waitForAngularEnabled()
await browser.waitForAngularEnabled(false)
await myAccountPage.goOnAssociatedPlaylistEmbed() await myAccountPage.goOnAssociatedPlaylistEmbed()
@ -202,49 +190,45 @@ describe('Videos workflow', () => {
console.log('Will set %s and %s tokens in local storage.', accessToken, refreshToken) console.log('Will set %s and %s tokens in local storage.', accessToken, refreshToken)
await browser.executeScript(`window.localStorage.setItem('access_token', '${accessToken}');`) await browser.execute(`window.localStorage.setItem('access_token', '${accessToken}');`)
await browser.executeScript(`window.localStorage.setItem('refresh_token', '${refreshToken}');`) await browser.execute(`window.localStorage.setItem('refresh_token', '${refreshToken}');`)
await browser.executeScript(`window.localStorage.setItem('token_type', 'Bearer');`) await browser.execute(`window.localStorage.setItem('token_type', 'Bearer');`)
await browser.refresh() await browser.refresh()
await playerPage.playVideo() await playerPage.playVideo()
await playerPage.waitUntilPlaylistInfo('2/2') await playerPage.waitUntilPlaylistInfo('2/2', 30 * 1000)
await browser.waitForAngularEnabled(oldValue)
}) })
it('Should watch the HLS playlist in the embed', async () => { it('Should watch the HLS playlist in the embed', async () => {
const oldValue = await browser.waitForAngularEnabled()
await browser.waitForAngularEnabled(false)
await videoWatchPage.goOnP2PMediaLoaderPlaylistEmbed() await videoWatchPage.goOnP2PMediaLoaderPlaylistEmbed()
await playerPage.playVideo() await playerPage.playVideo()
await playerPage.waitUntilPlaylistInfo('2/2') await playerPage.waitUntilPlaylistInfo('2/2', 30 * 1000)
await browser.waitForAngularEnabled(oldValue)
}) })
it('Should delete the video 2', async () => { it('Should delete the video 2', async () => {
if (await skipIfUploadNotSupported()) return if (isUploadUnsupported()) return
// Go to the dev website // Go to the dev website
await browser.get(videoWatchUrl) await go(videoWatchUrl)
await myAccountPage.navigateToMyVideos() await myAccountPage.navigateToMyVideos()
await myAccountPage.removeVideo(video2Name) await myAccountPage.removeVideo(video2Name)
await myAccountPage.validRemove() await myAccountPage.validRemove()
const count = await myAccountPage.countVideos([ videoName, video2Name ]) await browser.waitUntil(async () => {
expect(count).toEqual(1) const count = await myAccountPage.countVideos([ videoName, video2Name ])
return count === 1
})
}) })
it('Should delete the first video', async () => { it('Should delete the first video', async () => {
if (await skipIfUploadNotSupported()) return if (isUploadUnsupported()) return
await myAccountPage.removeVideo(videoName) await myAccountPage.removeVideo(videoName)
await myAccountPage.validRemove() await myAccountPage.validRemove()

View File

@ -5,9 +5,14 @@
"module": "commonjs", "module": "commonjs",
"target": "es5", "target": "es5",
"types": [ "types": [
"jasmine", "node",
"jasminewd2", "webdriverio/async",
"node" "@wdio/mocha-framework",
"expect-webdriverio"
] ]
} },
"include": [
"src/**/*.ts",
"./*.ts"
]
} }

View File

@ -0,0 +1,114 @@
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().split('T')[0],
sessionName,
consoleLogs: 'info',
networkLogs: true
}
}
function buildBStackDesktopOptions (sessionName: string, resolution?: string) {
return {
'bstack:options': {
...buildMainOptions(sessionName),
resolution
}
}
}
function buildBStackMobileOptions (sessionName: string, deviceName: string, osVersion: string) {
return {
'bstack:options': {
...buildMainOptions(sessionName),
realMobile: true,
osVersion,
deviceName
}
}
}
module.exports = {
config: {
...mainConfig,
user,
key,
maxInstances: 5,
capabilities: [
{
browserName: 'Chrome',
...buildBStackDesktopOptions('Latest Chrome Desktop', '1280x1024')
},
{
browserName: 'Firefox',
browserVersion: '68', // ESR
...buildBStackDesktopOptions('Firefox ESR Desktop', '1280x1024')
},
{
browserName: 'Safari',
browserVersion: '11.1',
...buildBStackDesktopOptions('Safari Desktop', '1280x1024')
},
{
browserName: 'Firefox',
...buildBStackDesktopOptions('Firefox Latest', '1280x1024')
},
{
browserName: 'Edge',
...buildBStackDesktopOptions('Edge Latest', '1280x1024')
},
{
browserName: 'Chrome',
...buildBStackMobileOptions('Latest Chrome Android', 'Samsung Galaxy S6', '5.0')
},
{
browserName: 'Safari',
...buildBStackMobileOptions('Safari iPhone', 'iPhone 8 Plus', '11')
},
{
browserName: 'Safari',
...buildBStackMobileOptions('Safari iPad', 'iPad 7th', '13')
}
],
host: 'hub-cloud.browserstack.com',
connectionRetryTimeout: 240000,
waitforTimeout: 20000,
services: [
[
'browserstack', { browserstackLocal: true }
]
],
after: function (result) {
if (result === 0) {
browser.executeScript('browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"passed","reason": ""}}', [])
} else {
browser.executeScript('browserstack_executor: {"action": "setSessionStatus", "arguments": {"status":"failed","reason": ""}}', [])
}
}
} as WebdriverIO.Config
}

View File

@ -0,0 +1,43 @@
import { config as mainConfig } from './wdio.main.conf'
const prefs = {
'intl.accept_languages': 'en'
}
module.exports = {
config: {
...mainConfig,
runner: 'local',
maxInstances: 1,
capabilities: [
{
browserName: 'chrome',
acceptInsecureCerts: true,
'goog:chromeOptions': {
prefs
}
},
{
browserName: 'firefox',
'moz:firefoxOptions': {
// args: [ '-headless' ],
binary: '/usr/bin/firefox-developer-edition',
prefs
}
},
{
browserName: 'firefox',
'moz:firefoxOptions': {
// args: [ '-headless' ],
binary: '/usr/bin/firefox-esr',
prefs
}
}
],
services: [ 'chromedriver', 'geckodriver' ]
} as WebdriverIO.Config
}

View File

@ -0,0 +1,115 @@
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/**/*.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: 1,
//
// 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://localhost: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: 1,
//
// 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
},
autoCompileOpts: {
autoCompile: true,
tsNodeOpts: {
project: require('path').join(__dirname, './tsconfig.json')
}
},
before: function () {
require('expect-webdriverio')
require('./src/commands/upload')
}
} as Partial<WebdriverIO.Config>

View File

@ -58,8 +58,6 @@
"@types/chart.js": "^2.9.16", "@types/chart.js": "^2.9.16",
"@types/core-js": "^2.5.2", "@types/core-js": "^2.5.2",
"@types/debug": "^4.1.5", "@types/debug": "^4.1.5",
"@types/jasmine": "^3.3.15",
"@types/jasminewd2": "^2.0.3",
"@types/jschannel": "^1.0.0", "@types/jschannel": "^1.0.0",
"@types/linkifyjs": "^2.1.2", "@types/linkifyjs": "^2.1.2",
"@types/lodash-es": "^4.17.0", "@types/lodash-es": "^4.17.0",
@ -71,12 +69,19 @@
"@types/webtorrent": "^0.109.0", "@types/webtorrent": "^0.109.0",
"@typescript-eslint/eslint-plugin": "4.29.3", "@typescript-eslint/eslint-plugin": "4.29.3",
"@typescript-eslint/parser": "4.29.3", "@typescript-eslint/parser": "4.29.3",
"@wdio/browserstack-service": "^7.11.1",
"@wdio/cli": "^7.11.1",
"@wdio/codemod": "^0.9.0",
"@wdio/local-runner": "^7.11.1",
"@wdio/mocha-framework": "^7.11.1",
"@wdio/spec-reporter": "^7.10.1",
"angular2-hotkeys": "^2.1.2", "angular2-hotkeys": "^2.1.2",
"angularx-qrcode": "11.0.0", "angularx-qrcode": "11.0.0",
"bootstrap": "^4.1.3", "bootstrap": "^4.1.3",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"cache-chunk-store": "^3.0.0", "cache-chunk-store": "^3.0.0",
"chart.js": "^3.5.1", "chart.js": "^3.5.1",
"chromedriver": "^92.0.1",
"core-js": "^3.1.4", "core-js": "^3.1.4",
"css-loader": "^6.2.0", "css-loader": "^6.2.0",
"debug": "^4.3.1", "debug": "^4.3.1",
@ -85,19 +90,15 @@
"eslint-plugin-import": "^2.24.2", "eslint-plugin-import": "^2.24.2",
"eslint-plugin-jsdoc": "^36.0.8", "eslint-plugin-jsdoc": "^36.0.8",
"eslint-plugin-prefer-arrow": "latest", "eslint-plugin-prefer-arrow": "latest",
"expect": "^27.1.0",
"expect-webdriverio": "^3.1.2",
"focus-visible": "^5.0.2", "focus-visible": "^5.0.2",
"geckodriver": "^2.0.3",
"hls.js": "^1.0.7", "hls.js": "^1.0.7",
"html-loader": "^2.1.2", "html-loader": "^2.1.2",
"html-webpack-plugin": "^5.3.1", "html-webpack-plugin": "^5.3.1",
"https-browserify": "^1.0.0", "https-browserify": "^1.0.0",
"jasmine-core": "~3.8.0",
"jasmine-spec-reporter": "~7.0.0",
"jschannel": "^1.0.2", "jschannel": "^1.0.2",
"karma": "~6.3.2",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.1",
"karma-jasmine-html-reporter": "^1.5.0",
"linkifyjs": "^2.1.5", "linkifyjs": "^2.1.5",
"lodash-es": "^4.17.4", "lodash-es": "^4.17.4",
"markdown-it": "12.2.0", "markdown-it": "12.2.0",
@ -106,7 +107,6 @@
"path-browserify": "^1.0.0", "path-browserify": "^1.0.0",
"primeng": "^12.0.0-rc.1", "primeng": "^12.0.0-rc.1",
"process": "^0.11.10", "process": "^0.11.10",
"protractor": "~7.0.0",
"purify-css": "^1.2.5", "purify-css": "^1.2.5",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
"rxjs": "^7.3.0", "rxjs": "^7.3.0",
@ -129,6 +129,8 @@
"videojs-dock": "^2.0.2", "videojs-dock": "^2.0.2",
"videojs-hotkeys": "^0.2.27", "videojs-hotkeys": "^0.2.27",
"videostream": "~3.2.1", "videostream": "~3.2.1",
"wdio-chromedriver-service": "^7.2.0",
"wdio-geckodriver-service": "^2.0.3",
"webpack-bundle-analyzer": "^4.4.2", "webpack-bundle-analyzer": "^4.4.2",
"webpack-cli": "^4.7.0", "webpack-cli": "^4.7.0",
"webtorrent": "^1.3.8", "webtorrent": "^1.3.8",

File diff suppressed because it is too large Load Diff

View File

@ -4,12 +4,6 @@ set -eu
npm run clean:server:test npm run clean:server:test
(
cd client
npm run webdriver-manager -- update
npm run webpack -- --config webpack/webpack.video-embed.js --mode development
)
npm run concurrently -- -k -s first \ npm run concurrently -- -k -s first \
"cd client && npm run ng -- e2e --port 3333 -c local" \ "cd client/e2e && ../node_modules/.bin/wdio run ./wdio.local.conf.ts" \
"NODE_ENV=test NODE_APP_INSTANCE=1 NODE_CONFIG='{ \"log\": { \"level\": \"warn\" }, \"signup\": { \"enabled\": false } }' node dist/server" "NODE_ENV=test NODE_APP_INSTANCE=1 NODE_CONFIG='{ \"log\": { \"level\": \"warn\" }, \"signup\": { \"enabled\": false } }' node dist/server"

View File

@ -197,9 +197,6 @@ async function addVideo (options: {
}, sequelizeOptions) }, sequelizeOptions)
} }
// Channel has a new content, set as updated
await videoCreated.VideoChannel.setAsUpdated(t)
await autoBlacklistVideoIfNeeded({ await autoBlacklistVideoIfNeeded({
video, video,
user, user,
@ -214,6 +211,9 @@ async function addVideo (options: {
return { videoCreated } return { videoCreated }
}) })
// Channel has a new content, set as updated
await videoCreated.VideoChannel.setAsUpdated()
createTorrentFederate(video, videoFile) createTorrentFederate(video, videoFile)
.then(() => { .then(() => {
if (video.state === VideoState.TO_MOVE_TO_EXTERNAL_STORAGE) { if (video.state === VideoState.TO_MOVE_TO_EXTERNAL_STORAGE) {

View File

@ -753,7 +753,7 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"`
return this.Actor.isOutdated() return this.Actor.isOutdated()
} }
setAsUpdated (transaction: Transaction) { setAsUpdated (transaction?: Transaction) {
return setAsUpdated('videoChannel', this.id, transaction) return setAsUpdated('videoChannel', this.id, transaction)
} }
} }