Merge pull request #8357 from nextcloud/ui-regression
Frontend testing using puppeteer
This commit is contained in:
commit
75589badf3
48
.drone.yml
48
.drone.yml
|
@ -703,6 +703,32 @@ pipeline:
|
|||
when:
|
||||
matrix:
|
||||
TEST: memcache-redis-cluster
|
||||
ui-regression:
|
||||
image: nextcloudci/ui-regression:ui-regression-1
|
||||
commands:
|
||||
- cd tests/ui-regression
|
||||
- npm install
|
||||
- chown -R pptruser out node_modules
|
||||
- bash -c "until curl -s http://ui-regression-php-master > /dev/null && curl -s http://ui-regression-php > /dev/null; do sleep 2; done"
|
||||
- sudo -u pptruser node runTests.js || true
|
||||
- echo "The result can be found at https://s3.bitgrid.net/nextcloud-ui-regression/nextcloud/server/${DRONE_PULL_REQUEST}/index.html"
|
||||
shm_size: '1gb'
|
||||
when:
|
||||
matrix:
|
||||
TESTS: ui-regression
|
||||
publish-s3:
|
||||
image: plugins/s3
|
||||
endpoint: https://ci-assets.nextcloud.com
|
||||
bucket: nextcloud-ui-regression
|
||||
path_style: true
|
||||
source: tests/ui-regression/out/**/*
|
||||
strip_prefix: tests/ui-regression/out/
|
||||
acl: public-read
|
||||
target: ${DRONE_REPO_OWNER}/${DRONE_REPO_NAME}/${DRONE_PULL_REQUEST}
|
||||
secrets: [ aws_access_key_id, aws_secret_access_key ]
|
||||
when:
|
||||
matrix:
|
||||
TESTS: ui-regression
|
||||
matrix:
|
||||
include:
|
||||
- TESTS: checkers
|
||||
|
@ -848,6 +874,7 @@ matrix:
|
|||
ENABLE_REDIS_CLUSTER: true
|
||||
- TESTS: sqlite-php7.0-webdav-apache
|
||||
ENABLE_REDIS: true
|
||||
- TESTS: ui-regression
|
||||
|
||||
services:
|
||||
cache:
|
||||
|
@ -855,6 +882,27 @@ services:
|
|||
when:
|
||||
matrix:
|
||||
ENABLE_REDIS: true
|
||||
ui-regression-php:
|
||||
image: nextcloudci/server:server-1
|
||||
commands:
|
||||
- . /etc/apache2/envvars
|
||||
- rm -fr /var/www/html
|
||||
- mkdir /var/www/html && cp -rT . /var/www/html && chown -R www-data:www-data /var/www/html
|
||||
- rm -fr /var/www/html/config/config.php /var/www/html/data/*
|
||||
- apache2 -DFOREGROUND
|
||||
when:
|
||||
matrix:
|
||||
TESTS: ui-regression
|
||||
ui-regression-php-master:
|
||||
image: nextcloudci/server:server-1
|
||||
commands:
|
||||
- . /etc/apache2/envvars
|
||||
- rm -fr /var/www/html/config/config.php /var/www/html/data/*
|
||||
- su www-data -c "cd /var/www/html/ && git checkout $DRONE_REPO_BRANCH && git pull && git submodule update"
|
||||
- apache2 -DFOREGROUND
|
||||
when:
|
||||
matrix:
|
||||
TESTS: ui-regression
|
||||
cache-cluster:
|
||||
image: morrisjobke/redis-cluster
|
||||
when:
|
||||
|
|
|
@ -139,6 +139,9 @@ Vagrantfile
|
|||
/tests/autotest*
|
||||
/tests/data/lorem-copy.txt
|
||||
/tests/data/testimage-copy.png
|
||||
/tests/ui-regression/out/
|
||||
/tests/ui-regression/node_modules/
|
||||
/tests/ui-regression/package-lock.json
|
||||
/config/config-autotest-backup.php
|
||||
/config/autoconfig.php
|
||||
clover.xml
|
||||
|
|
|
@ -454,13 +454,14 @@ form #selectDbType {
|
|||
text-align:center;
|
||||
white-space: nowrap;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
}
|
||||
form #selectDbType .info {
|
||||
white-space: normal;
|
||||
}
|
||||
form #selectDbType label {
|
||||
position: static;
|
||||
margin: 0 -3px 5px;
|
||||
flex-grow: 1;
|
||||
margin: 0 -1px 5px;
|
||||
font-size: 12px;
|
||||
background:#f8f8f8;
|
||||
color:#888;
|
||||
|
@ -469,7 +470,7 @@ form #selectDbType label {
|
|||
}
|
||||
form #selectDbType label span {
|
||||
cursor: pointer;
|
||||
padding: 10px 20px;
|
||||
padding: 10px 17px;
|
||||
}
|
||||
form #selectDbType label.ui-state-hover,
|
||||
form #selectDbType label.ui-state-active {
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* @copyright 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Define resolutions to be tested when diffing screenshots
|
||||
*/
|
||||
resolutions: [
|
||||
{title: 'mobile', w: 360, h: 480},
|
||||
{title: 'narrow', w: 800, h: 600},
|
||||
{title: 'normal', w: 1024, h: 768},
|
||||
{title: 'wide', w: 1920, h: 1080},
|
||||
{title: 'qhd', w: 2560, h: 1440},
|
||||
],
|
||||
|
||||
/**
|
||||
* URL that holds the base branch
|
||||
*/
|
||||
urlBase: 'http://ui-regression-php-master/',
|
||||
|
||||
/**
|
||||
* URL that holds the branch to be diffed
|
||||
*/
|
||||
urlChange: 'http://ui-regression-php/',
|
||||
|
||||
/**
|
||||
* Path to output directory for screenshot files
|
||||
*/
|
||||
outputDirectory: 'out',
|
||||
|
||||
/**
|
||||
* Run in headless mode (useful for debugging)
|
||||
*/
|
||||
headless: true,
|
||||
|
||||
slowMo: 0,
|
||||
|
||||
};
|
|
@ -0,0 +1,256 @@
|
|||
/**
|
||||
* @copyright 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
const puppeteer = require('puppeteer');
|
||||
const pixelmatch = require('pixelmatch');
|
||||
const expect = require('chai').expect;
|
||||
const PNG = require('pngjs2').PNG;
|
||||
const fs = require('fs');
|
||||
const config = require('./config.js');
|
||||
|
||||
|
||||
module.exports = {
|
||||
browser: null,
|
||||
pageBase: null,
|
||||
pageCompare: null,
|
||||
lastBase: 0,
|
||||
lastCompare: 0,
|
||||
init: async function (test) {
|
||||
this._outputDirectory = `${config.outputDirectory}/${test.title}`;
|
||||
if (!fs.existsSync(config.outputDirectory)) fs.mkdirSync(config.outputDirectory);
|
||||
if (!fs.existsSync(this._outputDirectory)) fs.mkdirSync(this._outputDirectory);
|
||||
await this.resetBrowser();
|
||||
},
|
||||
exit: async function () {
|
||||
await this.browser.close();
|
||||
},
|
||||
resetBrowser: async function () {
|
||||
if (this.browser) {
|
||||
await this.browser.close();
|
||||
}
|
||||
this.browser = await puppeteer.launch({
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
||||
headless: config.headless,
|
||||
slowMo: config.slowMo,
|
||||
});
|
||||
this.pageBase = await this.browser.newPage();
|
||||
this.pageCompare = await this.browser.newPage();
|
||||
this.pageBase.setDefaultNavigationTimeout(60000);
|
||||
this.pageCompare.setDefaultNavigationTimeout(60000);
|
||||
|
||||
const self = this;
|
||||
this.pageCompare.on('requestfinished', function() {
|
||||
self.lastCompare = Date.now();
|
||||
});
|
||||
this.pageBase.on('requestfinished', function() {
|
||||
self.lastBase = Date.now();
|
||||
});
|
||||
},
|
||||
|
||||
awaitNetworkIdle: async function (seconds) {
|
||||
var self = this;
|
||||
return new Promise(function (resolve, reject) {
|
||||
const timeout = setTimeout(function() {
|
||||
reject();
|
||||
}, 10000)
|
||||
const waitForFoo = function() {
|
||||
const currentTime = Date.now() - seconds*1000;
|
||||
if (self.lastBase < currentTime && self.lastCompare < currentTime) {
|
||||
clearTimeout(timeout);
|
||||
return resolve();
|
||||
}
|
||||
setTimeout(waitForFoo, 100);
|
||||
};
|
||||
waitForFoo();
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
login: async function (test) {
|
||||
test.timeout(20000);
|
||||
await this.resetBrowser();
|
||||
await Promise.all([
|
||||
this.performLogin(this.pageBase, config.urlBase),
|
||||
this.performLogin(this.pageCompare, config.urlChange)
|
||||
]);
|
||||
},
|
||||
|
||||
performLogin: async function (page, baseUrl) {
|
||||
await page.bringToFront();
|
||||
await page.goto(baseUrl + '/index.php/login', {waitUntil: 'networkidle0'});
|
||||
await page.type('#user', 'admin');
|
||||
await page.type('#password', 'admin');
|
||||
const inputElement = await page.$('input[type=submit]');
|
||||
await inputElement.click();
|
||||
await page.waitForNavigation({waitUntil: 'networkidle2'});
|
||||
return await page.waitForSelector('#header');
|
||||
},
|
||||
|
||||
takeAndCompare: async function (test, route, action, options) {
|
||||
// use Promise.all
|
||||
if (options === undefined)
|
||||
options = {};
|
||||
if (options.waitUntil === undefined) {
|
||||
options.waitUntil = 'networkidle0';
|
||||
}
|
||||
if (options.viewport) {
|
||||
if (options.viewport.scale === undefined) {
|
||||
options.viewport.scale = 1;
|
||||
}
|
||||
await Promise.all([
|
||||
this.pageBase.setViewport({
|
||||
width: options.viewport.w,
|
||||
height: options.viewport.h,
|
||||
deviceScaleFactor: options.viewport.scale
|
||||
}),
|
||||
this.pageCompare.setViewport({
|
||||
width: options.viewport.w,
|
||||
height: options.viewport.h,
|
||||
deviceScaleFactor: options.viewport.scale
|
||||
})
|
||||
]);
|
||||
await this.delay(100);
|
||||
}
|
||||
let fileName = test.test.title
|
||||
if (route !== undefined) {
|
||||
await Promise.all([
|
||||
this.pageBase.goto(`${config.urlBase}${route}`, {waitUntil: options.waitUntil}),
|
||||
this.pageCompare.goto(`${config.urlChange}${route}`, {waitUntil: options.waitUntil})
|
||||
]);
|
||||
}
|
||||
await this.pageBase.$eval('body', function (e) {
|
||||
$('.live-relative-timestamp').removeClass('live-relative-timestamp').text('5 minutes ago');
|
||||
$(':focus').blur();
|
||||
});
|
||||
await this.pageCompare.$eval('body', function (e) {
|
||||
$('.live-relative-timestamp').removeClass('live-relative-timestamp').text('5 minutes ago');
|
||||
$(':focus').blur();
|
||||
});
|
||||
var failed = null;
|
||||
try {
|
||||
await this.pageBase.bringToFront();
|
||||
await action(this.pageBase);
|
||||
await this.pageCompare.bringToFront();
|
||||
await action(this.pageCompare);
|
||||
} catch (err) {
|
||||
failed = err;
|
||||
}
|
||||
await this.awaitNetworkIdle(3);
|
||||
await this.pageBase.$eval('body', function (e) {
|
||||
$('.live-relative-timestamp').removeClass('live-relative-timestamp').text('5 minutes ago');
|
||||
$(':focus').blur();
|
||||
});
|
||||
await this.pageCompare.$eval('body', function (e) {
|
||||
$('.live-relative-timestamp').removeClass('live-relative-timestamp').text('5 minutes ago');
|
||||
$(':focus').blur();
|
||||
});
|
||||
await Promise.all([
|
||||
this.pageBase.screenshot({
|
||||
path: `${this._outputDirectory}/${fileName}.base.png`,
|
||||
fullPage: false,
|
||||
}),
|
||||
this.pageCompare.screenshot({
|
||||
path: `${this._outputDirectory}/${fileName}.change.png`,
|
||||
fullPage: false
|
||||
})
|
||||
]);
|
||||
|
||||
if (options.runOnly === true) {
|
||||
fs.unlinkSync(`${this._outputDirectory}/${fileName}.base.png`);
|
||||
fs.renameSync(`${this._outputDirectory}/${fileName}.change.png`, `${this._outputDirectory}/${fileName}.png`);
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
if (options.runOnly !== true) {
|
||||
await this.compareScreenshots(fileName);
|
||||
}
|
||||
} catch (err) {
|
||||
if (failed) {
|
||||
console.log('Failure during takeAndCompare action callback');
|
||||
console.log(failed);
|
||||
}
|
||||
console.log('Failure when comparing images');
|
||||
return reject(err);
|
||||
}
|
||||
if (options.runOnly !== true && failed) {
|
||||
console.log('Failure during takeAndCompare action callback');
|
||||
console.log(failed);
|
||||
failed.failedAction = true;
|
||||
return reject(failed);
|
||||
}
|
||||
return resolve();
|
||||
});
|
||||
},
|
||||
|
||||
compareScreenshots: function (fileName) {
|
||||
let self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
const img1 = fs.createReadStream(`${self._outputDirectory}/${fileName}.base.png`).pipe(new PNG()).on('parsed', doneReading);
|
||||
const img2 = fs.createReadStream(`${self._outputDirectory}/${fileName}.change.png`).pipe(new PNG()).on('parsed', doneReading);
|
||||
|
||||
let filesRead = 0;
|
||||
|
||||
function doneReading () {
|
||||
// Wait until both files are read.
|
||||
if (++filesRead < 2) return;
|
||||
|
||||
// The files should be the same size.
|
||||
expect(img1.width, 'image widths are the same').equal(img2.width);
|
||||
expect(img1.height, 'image heights are the same').equal(img2.height);
|
||||
|
||||
// Do the visual diff.
|
||||
const diff = new PNG({width: img1.width, height: img2.height});
|
||||
const numDiffPixels = pixelmatch(
|
||||
img1.data, img2.data, diff.data, img1.width, img1.height,
|
||||
{threshold: 0.3});
|
||||
if (numDiffPixels > 0) {
|
||||
diff.pack().pipe(fs.createWriteStream(`${self._outputDirectory}/${fileName}.diff.png`));
|
||||
} else {
|
||||
fs.unlinkSync(`${self._outputDirectory}/${fileName}.base.png`);
|
||||
fs.renameSync(`${self._outputDirectory}/${fileName}.change.png`, `${self._outputDirectory}/${fileName}.png`);
|
||||
}
|
||||
|
||||
// The files should look the same.
|
||||
expect(numDiffPixels, 'number of different pixels').equal(0);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Helper function to wait
|
||||
* to make sure that initial animations are done
|
||||
*/
|
||||
delay: async function (timeout) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, timeout);
|
||||
});
|
||||
},
|
||||
|
||||
childOfClassByText: async function (page, classname, text) {
|
||||
return page.$x('//*[contains(concat(" ", normalize-space(@class), " "), " ' + classname + ' ")]//text()[normalize-space() = \'' + text + '\']/..');
|
||||
},
|
||||
|
||||
childOfIdByText: async function (page, classname, text) {
|
||||
return page.$x('//*[contains(concat(" ", normalize-space(@id), " "), " ' + classname + ' ")]//text()[normalize-space() = \'' + text + '\']/..');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,219 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous" />
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" />
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
|
||||
<title>Nextcloud UI regression tests</title>
|
||||
<style>
|
||||
|
||||
h2 {
|
||||
margin-top: 40px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.error {
|
||||
color: #aa0000;
|
||||
}
|
||||
.success {
|
||||
color: #00aa00;
|
||||
}
|
||||
.success img {
|
||||
display: none;
|
||||
width: 100px;
|
||||
}
|
||||
.success pre {
|
||||
display: none;
|
||||
}
|
||||
.test-result h3 span {
|
||||
width: 40px;
|
||||
}
|
||||
.test-result {
|
||||
padding: 20px;
|
||||
}
|
||||
img {
|
||||
max-width: 33%;
|
||||
padding: 10px;
|
||||
background-color: #eee;
|
||||
margin: 0;
|
||||
}
|
||||
.overview ul {
|
||||
position: fixed;
|
||||
max-width: inherit;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
ul li {
|
||||
list-style-type: none;
|
||||
padding: 3px;
|
||||
}
|
||||
ul a:first-child {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
ul span {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 1px;
|
||||
display: inline-block;
|
||||
}
|
||||
span.fa-check {
|
||||
color: green;
|
||||
}
|
||||
span.fa-times {
|
||||
color: red;
|
||||
}
|
||||
.navbar a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .5s;
|
||||
}
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark sticky-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="#">Nextcloud UI regression test</a>
|
||||
<a class="nav-link" :href="config.repoUrl">{{config.repoUrl}}</a>
|
||||
<a class="nav-link" :href="config.repoUrl + '/pull/' + config.pr">#{{ config.pr }}</span></a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main role="main" class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-2 overview">
|
||||
<ul>
|
||||
<li v-for="suite in config.tests" v-if="result[suite]">
|
||||
<a :href="'#' + suite">{{ suite }}</a>
|
||||
<a v-for="test in result[suite].tests" :href="test.fullTitle | convertToAnchor" :title="test.fullTitle">
|
||||
<span class="fa fa-times" v-if="Object.keys(test.err).length > 0"></span>
|
||||
<span class="fa fa-check" v-else></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-10" id="container">
|
||||
<div v-for="suite in config.tests" v-if="result[suite]">
|
||||
<h2 :id="suite | convertToId">{{ suite }} <span>{{ result[suite].passes.length }}/{{ result[suite].tests.length }}</span></h2>
|
||||
<test-result v-for="test in result[suite].tests" :key="test.fullTitle" :suite="suite" :test="test"></test-result>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script type="text/x-template" id="test-result-template">
|
||||
<div class="test-result" :id="test.fullTitle | convertToId">
|
||||
<h3 :class="{ error: Object.keys(test.err).length > 0, success: Object.keys(test.err).length == 0}"
|
||||
v-on:click="hidden === undefined ? hidden = false : hidden = !hidden">
|
||||
<span class="fa fa-times" v-if="Object.keys(test.err).length > 0"></span>
|
||||
<span class="fa fa-check" v-else></span>
|
||||
{{ test.title }}
|
||||
<i v-if="test.duration">{{ test.duration }}ms</i>
|
||||
</h3>
|
||||
<transition name="fade">
|
||||
<div v-if="(hidden === undefined && Object.keys(test.err).length > 0) || hidden === false">
|
||||
<div v-if="Object.keys(test.err).length > 0 && !test.err.failedAction">
|
||||
<a :href="getImagePath('.base')"><img :src="getImagePath('.base')" /></a>
|
||||
<a :href="getImagePath('.diff')"><img :src="getImagePath('.diff')" /></a>
|
||||
<a :href="getImagePath('.change')"><img :src="getImagePath('.change')" /></a>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a :href="getImagePath('')"><img :src="getImagePath('')" /></a>
|
||||
</div>
|
||||
<pre>{{ jsonData }}</pre>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
Vue.filter('convertToId', function (id) {
|
||||
return id.replace(/\W/g,'_');
|
||||
});
|
||||
|
||||
Vue.filter('convertToAnchor', function (id) {
|
||||
return '#' + id.replace(/\W/g,'_');
|
||||
});
|
||||
|
||||
Vue.component('test-result', {
|
||||
template: '#test-result-template',
|
||||
props: ['test', 'suite'],
|
||||
data: function () {
|
||||
return {
|
||||
hidden: undefined
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
jsonData: function() {
|
||||
return JSON.stringify(this.test, null, 2)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getImagePath: function(type) {
|
||||
return this.suite + '/' + this.test.title + type + '.png';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
message: 'Hello Vue!',
|
||||
config: {},
|
||||
result: {
|
||||
login: {}
|
||||
},
|
||||
},
|
||||
created: function() {
|
||||
this.fetchConfig();
|
||||
},
|
||||
methods: {
|
||||
fetchConfig: function() {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open('GET', 'config.json', true);
|
||||
|
||||
request.onload = function() {
|
||||
if (request.status >= 200 && request.status < 400) {
|
||||
app.config = JSON.parse(request.responseText);
|
||||
app.config.tests.forEach(function(item, i){
|
||||
app.fetchResults(item);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = function() {
|
||||
};
|
||||
|
||||
request.send();
|
||||
},
|
||||
fetchResults: function(suite) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open('GET', suite + '.json', true);
|
||||
|
||||
request.onload = function() {
|
||||
if (request.status >= 200 && request.status < 400) {
|
||||
Vue.set(app.result, suite, JSON.parse(request.responseText));
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = function() {
|
||||
};
|
||||
|
||||
request.send();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "ui-regression",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "mocha test/"
|
||||
},
|
||||
"author": "",
|
||||
"dependencies": {
|
||||
"chai": "^4.1.2",
|
||||
"fs": "0.0.1-security",
|
||||
"mocha": "^5.2.0",
|
||||
"mocha-json-report": "0.0.2",
|
||||
"pixelmatch": "^4.0.2",
|
||||
"png-js": "^0.1.1",
|
||||
"pngjs2": "^2.0.0",
|
||||
"polyserve": "^0.23.0",
|
||||
"puppeteer": "^1.6.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/**
|
||||
* @copyright 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
const fs = require('fs')
|
||||
const Mocha = require('mocha')
|
||||
|
||||
const testFolder = './test/'
|
||||
|
||||
|
||||
var tests = [
|
||||
'install',
|
||||
'login',
|
||||
'files',
|
||||
'public',
|
||||
'settings',
|
||||
'apps',
|
||||
]
|
||||
|
||||
var args = process.argv.slice(2);
|
||||
if (args.length > 0) {
|
||||
tests = args
|
||||
}
|
||||
|
||||
var config = {
|
||||
tests: tests,
|
||||
pr: process.env.DRONE_PULL_REQUEST,
|
||||
repoUrl: process.env.DRONE_REPO_LINK,
|
||||
};
|
||||
|
||||
console.log('=> Write test config');
|
||||
console.log(config);
|
||||
fs.writeFile('out/config.json', JSON.stringify(config), 'utf8', () => {});
|
||||
|
||||
var mocha = new Mocha({
|
||||
timeout: 60000
|
||||
});
|
||||
let result = {};
|
||||
|
||||
tests.forEach(async function (test) {
|
||||
mocha.addFile('./test/' + test + 'Spec.js')
|
||||
result[test] = {
|
||||
failures: [],
|
||||
passes: [],
|
||||
tests: [],
|
||||
pending: [],
|
||||
stats: {}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// fixme fail if installation failed
|
||||
// write json to file
|
||||
|
||||
function clean (test) {
|
||||
return {
|
||||
title: test.title,
|
||||
fullTitle: test.fullTitle(),
|
||||
duration: test.duration,
|
||||
currentRetry: test.currentRetry(),
|
||||
failedAction: test.failedAction,
|
||||
err: errorJSON(test.err || {})
|
||||
};
|
||||
}
|
||||
|
||||
function errorJSON (err) {
|
||||
var res = {};
|
||||
Object.getOwnPropertyNames(err).forEach(function (key) {
|
||||
res[key] = err[key];
|
||||
}, err);
|
||||
return res;
|
||||
}
|
||||
|
||||
mocha.run()
|
||||
.on('test', function (test) {
|
||||
})
|
||||
.on('suite end', function(suite) {
|
||||
if (result[suite.title] === undefined)
|
||||
return;
|
||||
result[suite.title].stats = suite.stats;
|
||||
})
|
||||
.on('test end', function (test) {
|
||||
result[test.parent.title].tests.push(test);
|
||||
})
|
||||
.on('pass', function (test) {
|
||||
result[test.parent.title].passes.push(test);
|
||||
})
|
||||
.on('fail', function (test) {
|
||||
result[test.parent.title].failures.push(test);
|
||||
})
|
||||
.on('pending', function (test) {
|
||||
result[test.parent.title].pending.push(test);
|
||||
})
|
||||
.on('end', function () {
|
||||
tests.forEach(function (test) {
|
||||
var json = JSON.stringify({
|
||||
stats: result[test].stats,
|
||||
tests: result[test].tests.map(clean),
|
||||
pending: result[test].pending.map(clean),
|
||||
failures: result[test].failures.map(clean),
|
||||
passes: result[test].passes.map(clean)
|
||||
}, null, 2);
|
||||
fs.writeFile(`out/${test}.json`, json, 'utf8', function () {
|
||||
console.log(`Written test result to out/${test}.json`)
|
||||
});
|
||||
});
|
||||
|
||||
var errorMessage = 'This PR introduces some UI differences, please check at {LINK}, if there are regressions based on the changes.'
|
||||
fs.writeFile('out/GITHUB_COMMENT', errorMessage, 'utf8', () => {});
|
||||
});
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* @copyright 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
const helper = require('../helper.js');
|
||||
const config = require('../config.js');
|
||||
|
||||
describe('apps', function () {
|
||||
|
||||
before(async () => {
|
||||
await helper.init(this)
|
||||
await helper.login(this)
|
||||
});
|
||||
after(async () => await helper.exit());
|
||||
|
||||
config.resolutions.forEach(function (resolution) {
|
||||
it('apps.' + resolution.title, async function () {
|
||||
return helper.takeAndCompare(this, 'index.php/settings/apps', async function (page) {
|
||||
await page.waitForSelector('#apps-list .section', {timeout: 5000});
|
||||
await page.waitFor(500);
|
||||
}, {viewport: resolution, waitUntil: 'networkidle2'});
|
||||
});
|
||||
|
||||
['your-apps', 'enabled', 'disabled', 'app-bundles'].forEach(function(endpoint) {
|
||||
it('apps.' + endpoint + '.' + resolution.title, async function () {
|
||||
return helper.takeAndCompare(this, undefined, async function (page) {
|
||||
try {
|
||||
await page.waitForSelector('#app-navigation-toggle', {
|
||||
visible: true,
|
||||
timeout: 1000,
|
||||
}).then((element) => element.click())
|
||||
} catch (err) {}
|
||||
await helper.delay(500);
|
||||
await page.click('li#app-category-' + endpoint + ' a');
|
||||
await helper.delay(500);
|
||||
await page.waitForSelector('#app-content:not(.icon-loading)');
|
||||
}, {viewport: resolution});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* @copyright 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
const puppeteer = require('puppeteer');
|
||||
const helper = require('../helper.js');
|
||||
const config = require('../config.js');
|
||||
|
||||
describe('files', function () {
|
||||
|
||||
before(async () => {
|
||||
await helper.init(this)
|
||||
await helper.login(this)
|
||||
});
|
||||
after(async () => await helper.exit());
|
||||
|
||||
config.resolutions.forEach(function (resolution) {
|
||||
|
||||
it('file-sidebar-share.' + resolution.title, async function () {
|
||||
return helper.takeAndCompare(this, 'index.php/apps/files', async function (page) {
|
||||
let element = await page.$('[data-file="welcome.txt"] .action-share');
|
||||
await element.click('[data-file="welcome.txt"] .action-share');
|
||||
await page.waitForSelector('.shareWithField');
|
||||
await helper.delay(500);
|
||||
await page.$eval('body', e => { $('.shareWithField').blur() });
|
||||
}, {viewport: resolution});
|
||||
});
|
||||
it('file-popover.' + resolution.title, async function () {
|
||||
return helper.takeAndCompare(this, 'index.php/apps/files', async function (page) {
|
||||
await page.click('[data-file=\'welcome.txt\'] .action-menu');
|
||||
await page.waitForSelector('.fileActionsMenu');
|
||||
}, {viewport: resolution});
|
||||
});
|
||||
it('file-sidebar-details.' + resolution.title, async function() {
|
||||
return helper.takeAndCompare(this, undefined, async function (page) {
|
||||
await page.click('[data-file=\'welcome.txt\'] .fileActionsMenu [data-action=\'Details\']');
|
||||
await page.waitForSelector('[data-tabid=\'commentsTabView\']');
|
||||
await page.$eval('body', e => { $('.shareWithField').blur() });
|
||||
await helper.delay(500); // wait for animation
|
||||
}, {viewport: resolution});
|
||||
});
|
||||
it('file-sidebar-details-sharing.' + resolution.title, async function() {
|
||||
return helper.takeAndCompare(this, undefined, async function (page) {
|
||||
let tab = await helper.childOfClassByText(page, 'tabHeaders', 'Sharing');
|
||||
tab[0].click();
|
||||
await page.waitForSelector('input.shareWithField');
|
||||
await page.$eval('body', e => { $('.shareWithField').blur() });
|
||||
await helper.delay(500); // wait for animation
|
||||
}, {viewport: resolution});
|
||||
});
|
||||
it('file-sidebar-details-versions.' + resolution.title, async function() {
|
||||
return helper.takeAndCompare(this, undefined, async function (page) {
|
||||
let tab = await helper.childOfClassByText(page, 'tabHeaders', 'Versions');
|
||||
tab[0].click();
|
||||
await helper.delay(100); // wait for animation
|
||||
}, {viewport: resolution});
|
||||
});
|
||||
it('file-popover.favorite.' + resolution.title, async function () {
|
||||
return helper.takeAndCompare(this, 'index.php/apps/files', async function (page) {
|
||||
await page.click('[data-file=\'welcome.txt\'] .action-menu');
|
||||
await page.waitForSelector('.fileActionsMenu')
|
||||
await page.click('[data-file=\'welcome.txt\'] .fileActionsMenu [data-action=\'Favorite\']');;
|
||||
}, {viewport: resolution});
|
||||
});
|
||||
|
||||
it('file-favorites.' + resolution.title, async function () {
|
||||
return helper.takeAndCompare(this, 'index.php/apps/files', async function (page) {
|
||||
try {
|
||||
await page.waitForSelector('#app-navigation-toggle', {
|
||||
visible: true,
|
||||
timeout: 1000,
|
||||
}).then((element) => element.click())
|
||||
} catch (err) {}
|
||||
await page.click('#app-navigation [data-id=\'favorites\'] a');
|
||||
await helper.delay(500); // wait for animation
|
||||
}, {viewport: resolution});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* @copyright 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
const helper = require('../helper.js');
|
||||
const config = require('../config.js');
|
||||
|
||||
describe('install', function () {
|
||||
|
||||
before(async () => await helper.init(this));
|
||||
after(async () => await helper.exit());
|
||||
|
||||
config.resolutions.forEach(function (resolution) {
|
||||
it('show-page.' + resolution.title, async function () {
|
||||
// (test, route, prepare, action, options
|
||||
return helper.takeAndCompare(this, 'index.php', async (page) => {
|
||||
await helper.delay(100);
|
||||
await page.$eval('body', function (e) {
|
||||
$('#adminlogin').blur();
|
||||
});
|
||||
await helper.delay(100);
|
||||
}, { waitUntil: 'networkidle0', viewport: resolution});
|
||||
});
|
||||
|
||||
it('show-advanced.' + resolution.title, async function () {
|
||||
// (test, route, prepare, action, options
|
||||
return helper.takeAndCompare(this, undefined, async (page) => {
|
||||
await page.click('#showAdvanced');
|
||||
await helper.delay(300);
|
||||
}, { waitUntil: 'networkidle0', viewport: resolution});
|
||||
});
|
||||
it('show-advanced-mysql.' + resolution.title, async function () {
|
||||
// (test, route, prepare, action, options
|
||||
return helper.takeAndCompare(this, undefined, async (page) => {
|
||||
await page.click('label.mysql');
|
||||
await helper.delay(300);
|
||||
}, { waitUntil: 'networkidle0', viewport: resolution});
|
||||
});
|
||||
});
|
||||
|
||||
it('runs', async function () {
|
||||
this.timeout(5*60*1000);
|
||||
helper.pageBase.setDefaultNavigationTimeout(5*60*1000);
|
||||
helper.pageCompare.setDefaultNavigationTimeout(5*60*1000);
|
||||
// just run for one resolution since we can only install once
|
||||
return helper.takeAndCompare(this, 'index.php', async function (page) {
|
||||
const login = await page.type('#adminlogin', 'admin');
|
||||
const password = await page.type('#adminpass', 'admin');
|
||||
const inputElement = await page.$('input[type=submit]');
|
||||
await inputElement.click();
|
||||
await page.waitForNavigation({waitUntil: 'networkidle2'});
|
||||
await page.waitForSelector('#header');
|
||||
helper.pageBase.setDefaultNavigationTimeout(60000);
|
||||
helper.pageCompare.setDefaultNavigationTimeout(60000);
|
||||
}, { waitUntil: 'networkidle0', viewport: {w: 1920, h: 1080}});
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* @copyright 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
const helper = require('../helper.js');
|
||||
const config = require('../config.js');
|
||||
|
||||
describe('login', function () {
|
||||
|
||||
before(async () => await helper.init(this));
|
||||
after(async () => await helper.exit());
|
||||
|
||||
/**
|
||||
* Test login page rendering
|
||||
*/
|
||||
config.resolutions.forEach(function (resolution) {
|
||||
it('login-page.' + resolution.title, async function () {
|
||||
return helper.takeAndCompare(this, '/', async (page) => {
|
||||
// make sure the cursor is not blinking in the login field
|
||||
await page.$eval('body', function (e) {
|
||||
$('#user').blur();
|
||||
});
|
||||
return await helper.delay(100);
|
||||
}, {viewport: resolution});
|
||||
});
|
||||
|
||||
it('login-page.forgot.' + resolution.title, async function () {
|
||||
return helper.takeAndCompare(this, undefined, async (page) => {
|
||||
const lostPassword = await page.$('#lost-password');
|
||||
await lostPassword.click();
|
||||
await helper.delay(500);
|
||||
await page.$eval('body', function (e) {
|
||||
$('#user').blur();
|
||||
});
|
||||
}, {viewport: resolution});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Perform login
|
||||
*/
|
||||
config.resolutions.forEach(function (resolution) {
|
||||
it('login-success.' + resolution.title, async function () {
|
||||
this.timeout(30000);
|
||||
await helper.resetBrowser();
|
||||
return helper.takeAndCompare(this, '/', async function (page) {
|
||||
await page.waitForSelector('input#user');
|
||||
await page.type('#user', 'admin');
|
||||
await page.type('#password', 'admin');
|
||||
const inputElement = await page.$('input[type=submit]');
|
||||
await inputElement.click();
|
||||
await page.waitForNavigation({waitUntil: 'networkidle2'});
|
||||
await page.waitForSelector('#header');
|
||||
await page.$eval('body', function (e) {
|
||||
// force relative timestamp to fixed value, since it breaks screenshot diffing
|
||||
$('.live-relative-timestamp').removeClass('live-relative-timestamp').text('5 minutes ago');
|
||||
});
|
||||
return await helper.delay(100);
|
||||
}, {viewport: resolution});
|
||||
})
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* @copyright 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
const puppeteer = require('puppeteer');
|
||||
const helper = require('../helper.js');
|
||||
const config = require('../config.js');
|
||||
|
||||
describe('public', function () {
|
||||
|
||||
before(async () => {
|
||||
await helper.init(this)
|
||||
await helper.login(this)
|
||||
});
|
||||
after(async () => await helper.exit());
|
||||
|
||||
/**
|
||||
* Test invalid file share rendering
|
||||
*/
|
||||
config.resolutions.forEach(function (resolution) {
|
||||
it('file-share-invalid.' + resolution.title, async function () {
|
||||
return helper.takeAndCompare(this, 'index.php/s/invalid', async function () {
|
||||
}, {waitUntil: 'networkidle2', viewport: resolution});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Share a file via public link
|
||||
*/
|
||||
|
||||
var shareLink = {};
|
||||
it('file-share-link', async function () {
|
||||
return helper.takeAndCompare(this, 'index.php/apps/files', async function (page) {
|
||||
const element = await page.$('[data-file="welcome.txt"] .action-share');
|
||||
await element.click('[data-file="welcome.txt"] .action-share');
|
||||
await page.waitForSelector('input.linkCheckbox');
|
||||
const linkCheckbox = await page.$('.linkShareView label');
|
||||
await Promise.all([
|
||||
linkCheckbox.click(),
|
||||
page.waitForSelector('.linkText')
|
||||
]);
|
||||
await helper.delay(500);
|
||||
const text = await page.waitForSelector('.linkText');
|
||||
const link = await (await text.getProperty('value')).jsonValue();
|
||||
shareLink[page.url()] = link;
|
||||
return await helper.delay(500);
|
||||
}, {
|
||||
runOnly: true,
|
||||
waitUntil: 'networkidle2',
|
||||
viewport: {w: 1920, h: 1080}
|
||||
});
|
||||
});
|
||||
|
||||
config.resolutions.forEach(function (resolution) {
|
||||
it('file-share-valid.' + resolution.title, async function () {
|
||||
return helper.takeAndCompare(this, 'index.php/apps/files', async function (page) {
|
||||
await page.goto(shareLink[page.url()]);
|
||||
await helper.delay(500);
|
||||
}, {waitUntil: 'networkidle2', viewport: resolution});
|
||||
});
|
||||
it('file-share-valid-actions.' + resolution.title, async function () {
|
||||
return helper.takeAndCompare(this, undefined, async function (page) {
|
||||
const moreButton = await page.waitForSelector('#header-secondary-action');
|
||||
await moreButton.click();
|
||||
await page.evaluate((data) => {
|
||||
return document.querySelector('#directLink').value = 'http://nextcloud.example.com/';
|
||||
});
|
||||
await helper.delay(500);
|
||||
}, {waitUntil: 'networkidle2', viewport: resolution});
|
||||
});
|
||||
});
|
||||
|
||||
it('file-unshare', async function () {
|
||||
return helper.takeAndCompare(this, 'index.php/apps/files', async function (page) {
|
||||
const element = await page.$('[data-file="welcome.txt"] .action-share');
|
||||
await element.click('[data-file="welcome.txt"] .action-share');
|
||||
await page.waitForSelector('input.linkCheckbox');
|
||||
const linkCheckbox = await page.$('.linkShareView label');
|
||||
await linkCheckbox.click();
|
||||
await helper.delay(500);
|
||||
}, { waitUntil: 'networkidle2', viewport: {w: 1920, h:1080}});
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* @copyright 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
const helper = require('../helper.js');
|
||||
const config = require('../config.js');
|
||||
|
||||
describe('settings', function () {
|
||||
|
||||
before(async () => {
|
||||
await helper.init(this)
|
||||
await helper.login(this)
|
||||
});
|
||||
after(async () => await helper.exit());
|
||||
|
||||
config.resolutions.forEach(function (resolution) {
|
||||
it('personal.' + resolution.title, async function () {
|
||||
return helper.takeAndCompare(this, 'index.php/settings/user', async function (page) {
|
||||
}, {viewport: resolution});
|
||||
});
|
||||
|
||||
it('admin.' + resolution.title, async function () {
|
||||
return helper.takeAndCompare(this, 'index.php/settings/admin', async function (page) {
|
||||
}, {viewport: resolution});
|
||||
});
|
||||
|
||||
['sharing', 'security', 'theming', 'encryption', 'additional', 'tips-tricks'].forEach(function(endpoint) {
|
||||
it('admin.' + endpoint + '.' + resolution.title, async function () {
|
||||
return helper.takeAndCompare(this, 'index.php/settings/admin/' + endpoint, async function (page) {
|
||||
}, {viewport: resolution, waitUntil: 'networkidle2'});
|
||||
});
|
||||
});
|
||||
|
||||
it('usermanagement.' + resolution.title, async function () {
|
||||
return helper.takeAndCompare(this, 'index.php/settings/users', async function (page) {
|
||||
}, {viewport: resolution});
|
||||
});
|
||||
|
||||
it('usermanagement.add.' + resolution.title, async function () {
|
||||
return helper.takeAndCompare(this, undefined, async function (page) {
|
||||
try {
|
||||
await page.waitForSelector('#app-navigation-toggle', {
|
||||
visible: true,
|
||||
timeout: 1000,
|
||||
}).then((element) => element.click())
|
||||
} catch (err) {}
|
||||
let newUserButton = await page.waitForSelector('#new-user-button');
|
||||
await newUserButton.click();
|
||||
await helper.delay(200);
|
||||
await page.$eval('body', function (e) {
|
||||
$('#newusername').blur();
|
||||
})
|
||||
await helper.delay(100);
|
||||
}, {viewport: resolution});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue