Merge pull request #3696 from nextcloud/feature/setup-cypress-test
Setup cypress test
This commit is contained in:
112
.github/workflows/cypress.yml
vendored
Normal file
112
.github/workflows/cypress.yml
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
name: Cypress
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- stable*
|
||||
|
||||
env:
|
||||
APP_NAME: deck
|
||||
CYPRESS_baseUrl: http://localhost:8081/index.php
|
||||
|
||||
jobs:
|
||||
cypress:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
# containers: [1, 2, 3]
|
||||
php-versions: [ '7.4' ]
|
||||
databases: [ 'sqlite' ]
|
||||
server-versions: [ 'master' ]
|
||||
|
||||
steps:
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: nextcloud/server
|
||||
ref: ${{ matrix.server-versions }}
|
||||
|
||||
- name: Checkout submodules
|
||||
shell: bash
|
||||
run: |
|
||||
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
|
||||
git submodule sync --recursive
|
||||
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
|
||||
|
||||
- name: Checkout ${{ env.APP_NAME }}
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, zip, gd, apcu
|
||||
ini-values:
|
||||
apc.enable_cli=on
|
||||
coverage: none
|
||||
|
||||
- name: Set up Nextcloud
|
||||
env:
|
||||
DB_PORT: 4444
|
||||
PHP_CLI_SERVER_WORKERS: 10
|
||||
run: |
|
||||
mkdir data
|
||||
php occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
|
||||
php occ config:system:set memcache.local --value="\\OC\\Memcache\\APCu"
|
||||
php occ config:system:set debug --value=true --type=boolean
|
||||
php -f index.php
|
||||
php -S 0.0.0.0:8081 &
|
||||
export OC_PASS=1234561
|
||||
php occ user:add --password-from-env user1
|
||||
php occ user:add --password-from-env user2
|
||||
php occ app:enable deck
|
||||
php occ app:list
|
||||
cd apps/deck
|
||||
composer install
|
||||
npm ci
|
||||
npm run build
|
||||
cd ../../
|
||||
curl -v http://localhost:8081/index.php/login
|
||||
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@v2
|
||||
with:
|
||||
record: true
|
||||
parallel: false
|
||||
wait-on: '${{ env.CYPRESS_baseUrl }}'
|
||||
working-directory: 'apps/${{ env.APP_NAME }}'
|
||||
config: defaultCommandTimeout=10000,video=false
|
||||
env:
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
npm_package_name: ${{ env.APP_NAME }}
|
||||
|
||||
- name: Upload test failure screenshots
|
||||
uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
name: Upload screenshots
|
||||
path: apps/${{ env.APP_NAME }}/cypress/screenshots/
|
||||
retention-days: 5
|
||||
|
||||
- name: Upload nextcloud logs
|
||||
uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
name: Upload nextcloud log
|
||||
path: data/nextcloud.log
|
||||
retention-days: 5
|
||||
7
cypress.json
Normal file
7
cypress.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"baseUrl": "http://nextcloud.local/index.php",
|
||||
"projectId": "1s7wkc",
|
||||
"viewportWidth": 1280,
|
||||
"viewportHeight": 720,
|
||||
"experimentalSessionAndOrigin": true
|
||||
}
|
||||
5
cypress/fixtures/example.json
Normal file
5
cypress/fixtures/example.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
41
cypress/integration/boardFeatures.js
Normal file
41
cypress/integration/boardFeatures.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { randHash } from "../utils"
|
||||
const randUser = randHash()
|
||||
|
||||
describe('Board', function () {
|
||||
const password = 'pass123'
|
||||
|
||||
before(function () {
|
||||
cy.nextcloudCreateUser(randUser, password)
|
||||
})
|
||||
|
||||
beforeEach(function() {
|
||||
cy.login(randUser, password)
|
||||
})
|
||||
|
||||
it('Can create a board', function () {
|
||||
let board = 'Test'
|
||||
|
||||
cy.intercept({
|
||||
method: 'POST',
|
||||
url: '/index.php/apps/deck/boards',
|
||||
}).as('createBoardRequest')
|
||||
|
||||
// Click "Add board"
|
||||
cy.openLeftSidebar()
|
||||
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
|
||||
.eq(3).find('a').first().click({force: true})
|
||||
|
||||
// Type the board title
|
||||
cy.get('.board-create form input[type=text]')
|
||||
.type(board, {force: true})
|
||||
|
||||
// Submit
|
||||
cy.get('.board-create form input[type=submit]')
|
||||
.first().click({force: true})
|
||||
|
||||
cy.wait('@createBoardRequest').its('response.statusCode').should('equal', 200)
|
||||
|
||||
cy.get('.app-navigation__list .app-navigation-entry__children .app-navigation-entry')
|
||||
.contains(board).should('be.visible')
|
||||
})
|
||||
})
|
||||
38
cypress/integration/cardFeatures.js
Normal file
38
cypress/integration/cardFeatures.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import { randHash } from '../utils'
|
||||
const randUser = randHash()
|
||||
|
||||
describe('Card', function () {
|
||||
const board = 'TestBoard'
|
||||
const list = 'TestList'
|
||||
const password = 'pass123'
|
||||
|
||||
before(function () {
|
||||
cy.nextcloudCreateUser(randUser, password)
|
||||
cy.deckCreateBoard({ user: randUser, password }, board)
|
||||
cy.deckCreateList({ user: randUser, password }, list)
|
||||
})
|
||||
|
||||
beforeEach(function () {
|
||||
cy.login(randUser, password)
|
||||
})
|
||||
|
||||
it('Can add a card', function () {
|
||||
let card = 'Card 1'
|
||||
|
||||
cy.openLeftSidebar()
|
||||
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
|
||||
.eq(3).find('a.app-navigation-entry-link')
|
||||
.first().click({force: true})
|
||||
|
||||
cy.get('.board .stack').eq(0).within(() => {
|
||||
cy.get('button.action-item.action-item--single.icon-add')
|
||||
.first().click()
|
||||
|
||||
cy.get('.stack__card-add form input#new-stack-input-main')
|
||||
.type(card)
|
||||
cy.get('.stack__card-add form input[type=submit]')
|
||||
.first().click()
|
||||
cy.get('.card').first().contains(card).should('be.visible')
|
||||
})
|
||||
})
|
||||
})
|
||||
32
cypress/integration/deckDashboard.js
Normal file
32
cypress/integration/deckDashboard.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { randHash } from '../utils'
|
||||
const randUser = randHash()
|
||||
|
||||
describe('Deck dashboard', function() {
|
||||
const password = 'pass123'
|
||||
|
||||
before(function () {
|
||||
cy.nextcloudCreateUser(randUser, password)
|
||||
})
|
||||
|
||||
beforeEach(function() {
|
||||
cy.login(randUser, password)
|
||||
})
|
||||
|
||||
it('Can show the right title on the dashboard', function() {
|
||||
cy.get('.board-title h2')
|
||||
.should('have.length', 1).first()
|
||||
.should('have.text', 'Upcoming cards')
|
||||
})
|
||||
|
||||
it('Can see the default "Personal Board" created for user by default', function () {
|
||||
const defaultBoard = 'Personal'
|
||||
|
||||
cy.openLeftSidebar()
|
||||
cy.get('.app-navigation__list .app-navigation-entry')
|
||||
.eq(1)
|
||||
.find('ul.app-navigation-entry__children li.app-navigation-entry')
|
||||
.first()
|
||||
.contains(defaultBoard)
|
||||
.should('be.visible')
|
||||
})
|
||||
})
|
||||
33
cypress/integration/stackFeatures.js
Normal file
33
cypress/integration/stackFeatures.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { randHash } from "../utils";
|
||||
const randUser = randHash();
|
||||
|
||||
describe("Stack", function () {
|
||||
const board = "TestBoard";
|
||||
const password = "pass123";
|
||||
const stack = "List 1";
|
||||
|
||||
before(function () {
|
||||
cy.nextcloudCreateUser(randUser, password)
|
||||
cy.deckCreateBoard({ user: randUser, password }, board)
|
||||
})
|
||||
|
||||
beforeEach(function () {
|
||||
cy.logout()
|
||||
cy.login(randUser, password)
|
||||
})
|
||||
|
||||
it("Can create a stack", function () {
|
||||
cy.openLeftSidebar()
|
||||
cy.get("#app-navigation-vue .app-navigation__list .app-navigation-entry")
|
||||
.eq(3)
|
||||
.find("a.app-navigation-entry-link")
|
||||
.first()
|
||||
.click({ force: true })
|
||||
|
||||
cy.get("#stack-add button").first().click()
|
||||
cy.get("#stack-add form input#new-stack-input-main").type(stack)
|
||||
cy.get("#stack-add form input[type=submit]").first().click()
|
||||
|
||||
cy.get(".board .stack").eq(0).contains(stack).should("be.visible")
|
||||
})
|
||||
});
|
||||
22
cypress/plugins/index.js
Normal file
22
cypress/plugins/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
}
|
||||
113
cypress/support/commands.js
Normal file
113
cypress/support/commands.js
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @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 url = Cypress.config("baseUrl").replace(/\/index.php\/?$/g, "");
|
||||
Cypress.env("baseUrl", url);
|
||||
|
||||
Cypress.Commands.add("login", (user, password, route = "/apps/deck/") => {
|
||||
let session = `${user}-${Date.now()}`;
|
||||
cy.session(session, function () {
|
||||
cy.visit(route);
|
||||
cy.get("input[name=user]").type(user);
|
||||
cy.get("input[name=password]").type(password);
|
||||
cy.get(".submit-wrapper input[type=submit]").click();
|
||||
cy.url().should("include", route);
|
||||
});
|
||||
// in case the session already existed but we are on a different route...
|
||||
cy.visit(route);
|
||||
});
|
||||
|
||||
Cypress.Commands.add("logout", (route = "/") => {
|
||||
cy.session("_guest", function () {});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("nextcloudCreateUser", (user, password) => {
|
||||
cy.clearCookies();
|
||||
cy.request({
|
||||
method: "POST",
|
||||
url: `${Cypress.env("baseUrl")}/ocs/v1.php/cloud/users?format=json`,
|
||||
form: true,
|
||||
body: {
|
||||
userid: user,
|
||||
password: password,
|
||||
},
|
||||
auth: { user: "admin", pass: "admin" },
|
||||
headers: {
|
||||
"OCS-ApiRequest": "true",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
}).then((response) => {
|
||||
cy.log(`Created user ${user}`, response.status);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("nextcloudUpdateUser", (user, password, key, value) => {
|
||||
cy.request({
|
||||
method: "PUT",
|
||||
url: `${Cypress.env("baseUrl")}/ocs/v2.php/cloud/users/${user}`,
|
||||
form: true,
|
||||
body: { key, value },
|
||||
auth: { user, pass: password },
|
||||
headers: {
|
||||
"OCS-ApiRequest": "true",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
}).then((response) => {
|
||||
cy.log(`Updated user ${user} ${key} to ${value}`, response.status);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("openLeftSidebar", () => {
|
||||
cy.get(".app-navigation button.app-navigation-toggle").click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add("deckCreateBoard", ({ user, password }, title) => {
|
||||
cy.login(user, password);
|
||||
|
||||
cy.get(".app-navigation button.app-navigation-toggle").click();
|
||||
cy.get("#app-navigation-vue .app-navigation__list .app-navigation-entry")
|
||||
.eq(3)
|
||||
.find("a")
|
||||
.first()
|
||||
.click({ force: true });
|
||||
|
||||
cy.get(".board-create form input[type=text]").type(title, { force: true });
|
||||
|
||||
cy.get(".board-create form input[type=submit]")
|
||||
.first()
|
||||
.click({ force: true });
|
||||
});
|
||||
|
||||
Cypress.Commands.add("deckCreateList", ({ user, password }, title) => {
|
||||
cy.login(user, password);
|
||||
|
||||
cy.get(".app-navigation button.app-navigation-toggle").click();
|
||||
cy.get("#app-navigation-vue .app-navigation__list .app-navigation-entry")
|
||||
.eq(3)
|
||||
.find("a.app-navigation-entry-link")
|
||||
.first()
|
||||
.click({ force: true });
|
||||
|
||||
cy.get("#stack-add button").first().click();
|
||||
cy.get("#stack-add form input#new-stack-input-main").type(title);
|
||||
cy.get("#stack-add form input[type=submit]").first().click();
|
||||
});
|
||||
20
cypress/support/index.js
Normal file
20
cypress/support/index.js
Normal file
@@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
1
cypress/utils/index.js
Normal file
1
cypress/utils/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export const randHash = () => Math.random().toString(36).replace(/[^a-z]+/g, '').slice(0, 10)
|
||||
2534
package-lock.json
generated
2534
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -46,8 +46,8 @@
|
||||
"dompurify": "^2.3.6",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^13.0.1",
|
||||
"markdown-it-task-lists": "^2.1.1",
|
||||
"markdown-it-link-attributes": "^4.0.0",
|
||||
"markdown-it-task-lists": "^2.1.1",
|
||||
"moment": "^2.29.3",
|
||||
"nextcloud-vue-collections": "^0.10.0",
|
||||
"p-queue": "^6.6.2",
|
||||
@@ -77,6 +77,7 @@
|
||||
"@nextcloud/webpack-vue-config": "^5.1.0",
|
||||
"@relative-ci/agent": "^3.1.3",
|
||||
"@vue/test-utils": "^1.3.0",
|
||||
"cypress": "^9.6.0",
|
||||
"jest": "^28.1.0",
|
||||
"jest-serializer-vue": "^2.0.2",
|
||||
"vue-jest": "^3.0.7"
|
||||
|
||||
@@ -163,8 +163,8 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@import '../../css/animations.scss';
|
||||
@import '../../css/variables.scss';
|
||||
@import '../../css/animations';
|
||||
@import '../../css/variables';
|
||||
|
||||
form {
|
||||
text-align: center;
|
||||
|
||||
@@ -276,7 +276,7 @@ export default {
|
||||
@import './../../css/variables';
|
||||
|
||||
.stack {
|
||||
width: $stack-width + $stack-spacing*3;
|
||||
width: $stack-width + $stack-spacing * 3;
|
||||
margin-left: math.div($stack-spacing, 2);
|
||||
margin-right: math.div($stack-spacing, 2);
|
||||
}
|
||||
|
||||
@@ -216,12 +216,12 @@ export default {
|
||||
.modal__card .app-sidebar {
|
||||
$modal-padding: 14px;
|
||||
border: 0;
|
||||
min-width: calc(100% - #{$modal-padding*2});
|
||||
min-width: calc(100% - #{$modal-padding * 2});
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-width: calc(100% - #{$modal-padding*2});
|
||||
max-width: calc(100% - #{$modal-padding * 2});
|
||||
padding: 0 14px;
|
||||
max-height: 100%;
|
||||
overflow: initial;
|
||||
|
||||
@@ -263,6 +263,7 @@ export default {
|
||||
overflow-x: auto;
|
||||
|
||||
&::v-deep {
|
||||
/* stylelint-disable-next-line no-invalid-position-at-import-rule */
|
||||
@import './../../css/markdown';
|
||||
}
|
||||
|
||||
|
||||
@@ -232,6 +232,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line no-invalid-position-at-import-rule */
|
||||
@import './../../css/labels';
|
||||
|
||||
.card-controls {
|
||||
|
||||
@@ -173,7 +173,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../css/variables.scss';
|
||||
@import '../../css/variables';
|
||||
|
||||
.global-search {
|
||||
width: 100%;
|
||||
|
||||
@@ -54,7 +54,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../css/variables.scss';
|
||||
@import '../../css/variables';
|
||||
$clickable-area: 44px;
|
||||
|
||||
.card--placeholder {
|
||||
|
||||
Reference in New Issue
Block a user