Fix cypress workflow

Signed-off-by: Louis Chemineau <louis@chmn.me>
pull/1558/head
Louis Chemineau 2023-01-04 15:36:33 +01:00
rodzic df8d5fdad4
commit 74308c64ff
14 zmienionych plików z 5208 dodań i 744 usunięć

Wyświetl plik

@ -11,50 +11,78 @@ env:
APP_NAME: social APP_NAME: social
BRANCH: ${{ github.base_ref }} BRANCH: ${{ github.base_ref }}
CYPRESS_baseUrl: http://127.0.0.1:8082/index.php CYPRESS_baseUrl: http://127.0.0.1:8082/index.php
TESTING: true
jobs: jobs:
cypress: init:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# run 2 copies of the current job in parallel
containers: [1, 2]
node-version: ['12']
php-versions: ['7.4']
name: Runner ${{ matrix.containers }}
steps: steps:
- name: Checkout app - name: Checkout app
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setup server - name: Read package.json node and npm engines version
run: | uses: skjnldsv/read-package-engines-version-actions@v1.1
cd cypress id: versions
docker-compose up -d
- name: Set up node ${{ matrix.node-version }}
uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} fallbackNode: "^12"
fallbackNpm: "^6"
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
uses: actions/setup-node@v3
with:
cache: "npm"
node-version: ${{ steps.versions.outputs.nodeVersion }}
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
- name: Install dependencies & build app - name: Install dependencies & build app
run: | run: |
npm ci npm ci
composer install composer install
TESTING=true npm run build --if-present TESTING=true npm run build --if-present
- name: Wait for server
- name: Save context
uses: actions/cache@v3
with:
key: cypress-context-${{ github.run_id }}
path: /home/runner/work/social
cypress:
runs-on: ubuntu-latest
needs: init
strategy:
fail-fast: false
matrix:
# run multiple copies of the current job in parallel
containers: [1, 2, 3, 4, 5, 6, 7, 8]
name: runner ${{ matrix.containers }}
steps:
- name: Restore context
uses: actions/cache@v3
with:
key: cypress-context-${{ github.run_id }}
path: /home/runner/work/social
- name: Setup server
run: | run: |
npm install -g wait-on cd cypress
wait-on -i 500 -t 240000 $CYPRESS_baseUrl docker-compose up -d
- name: Wait for server
run: npm run wait-on $CYPRESS_baseUrl
- name: Enable app & configure server - name: Enable app & configure server
run: | run: |
cd cypress cd cypress
docker-compose exec --env APP_NAME=${{ env.APP_NAME }} -T nextcloud bash /initserver.sh docker-compose exec --env APP_NAME=${{ env.APP_NAME }} --env BRANCH=${{ env.BRANCH }} -T nextcloud bash /initserver.sh
- name: Cypress run - name: Cypress run
uses: cypress-io/github-action@v1 uses: cypress-io/github-action@v4
with: with:
record: true record: true
parallel: true parallel: true

28
cypress.config.js 100644
Wyświetl plik

@ -0,0 +1,28 @@
const { defineConfig } = require('cypress')
const browserify = require('@cypress/browserify-preprocessor')
module.exports = defineConfig({
projectId: '7mqhfh',
viewportWidth: 1280,
viewportHeight: 720,
defaultCommandTimeout: 6000,
retries: 1,
env: {
failSilently: false,
type: 'actual',
},
screenshotsFolder: 'cypress/snapshots/actual',
trashAssetsBeforeRuns: true,
e2e: {
baseUrl: 'http://localhost:8082/index.php',
setupNodeEvents(on, config) {
// Fix browserslist extend https://github.com/cypress-io/cypress/issues/2983#issuecomment-570616682
on('file:preprocessor', browserify())
},
},
})

Wyświetl plik

@ -1,7 +0,0 @@
{
"baseUrl": "http://localhost:8082/index.php/",
"projectId": "7mqhfh",
"viewportWidth": 1280,
"viewportHeight": 720,
"defaultCommandTimeout": 6000
}

Wyświetl plik

@ -1,16 +1,18 @@
version: '3' version: '3.7'
services: services:
nextcloud: nextcloud:
image: nextcloudci/server image: ghcr.io/nextcloud/continuous-integration-shallow-server
ports: ports:
- 8082:80 - 8082:80
environment: environment:
CYPRESS_baseUrl: "http://127.0.0.1:8082/index.php" CYPRESS_baseUrl: "http://127.0.0.1:8082/index.php"
BRANCH: master BRANCH: "${BRANCH:-master}"
volumes: volumes:
- ../:/var/www/html/apps/social # Using fallback to make sure this script doesn't mess
# with the mounting if APP_NAME is not provided.
- ../:/var/www/html/apps/${APP_NAME:-social}
- ./initserver.sh:/initserver.sh - ./initserver.sh:/initserver.sh

Wyświetl plik

@ -1,4 +1,4 @@
let userId = 'janedoe' + Date.now(); const userId = 'janedoe' + Date.now()
describe('Social app setup', function() { describe('Social app setup', function() {
before(function() { before(function() {
@ -6,10 +6,6 @@ describe('Social app setup', function() {
cy.login(userId, 'p4ssw0rd') cy.login(userId, 'p4ssw0rd')
}) })
beforeEach(() => {
Cypress.Cookies.preserveOnce('nc_username', 'nc_token', 'nc_session_id', 'oc_sessionPassphrase');
})
it('See the welcome message', function() { it('See the welcome message', function() {
cy.visit('/apps/social/') cy.visit('/apps/social/')
cy.get('.social__welcome').should('contain', 'Nextcloud becomes part of the federated social networks!') cy.get('.social__welcome').should('contain', 'Nextcloud becomes part of the federated social networks!')
@ -26,7 +22,7 @@ describe('Social app setup', function() {
cy.get('.app-navigation').contains('Direct messages').click() cy.get('.app-navigation').contains('Direct messages').click()
cy.get('.emptycontent').should('be.visible').contains('No direct messages found') cy.get('.emptycontent').should('be.visible').contains('No direct messages found')
cy.get('.app-navigation').contains('Profile').click() cy.get('.app-navigation').contains('Profile').click()
cy.get('.emptycontent').should('be.visible').contains('You haven\'t tooted yet') cy.get('.emptycontent').should('be.visible').contains('You have not tooted yet')
}) })
}) })

Wyświetl plik

@ -20,15 +20,17 @@
* *
*/ */
let userId = 'janedoe' + Date.now(); const userId = 'janedoe' + Date.now()
describe('Create posts', function() { describe('Create posts', function() {
before(function() { before(function() {
// ensure that the admin account is initialized for social // ensure that the admin account is initialized for social
cy.login('admin', 'admin', '/apps/social/') cy.login('admin', 'admin', '/apps/social/')
cy.nextcloudCreateUser(userId, 'p4ssw0rd') cy.nextcloudCreateUser(userId, 'p4ssw0rd')
cy.logout()
cy.login(userId, 'p4ssw0rd', '/apps/social/') cy.login(userId, 'p4ssw0rd', '/apps/social/')
cy.get('.app-content').should('be.visible') cy.get('.app-content').should('be.visible')
}) })
@ -37,10 +39,6 @@ describe('Create posts', function() {
cy.screenshot() cy.screenshot()
}) })
beforeEach(() => {
Cypress.Cookies.preserveOnce('nc_username', 'nc_token', 'nc_session_id', 'oc_sessionPassphrase');
})
it('See the empty content illustration', function() { it('See the empty content illustration', function() {
cy.get('.emptycontent').should('be.visible').contains('No posts found') cy.get('.emptycontent').should('be.visible').contains('No posts found')
}) })
@ -49,19 +47,19 @@ describe('Create posts', function() {
cy.visit('/apps/social/') cy.visit('/apps/social/')
cy.server() cy.server()
cy.route('POST', '/index.php/apps/social/api/v1/post').as('postMessage') cy.route('POST', '/index.php/apps/social/api/v1/post').as('postMessage')
cy.get('.new-post input[type=submit]') cy.get('.new-post button[type=submit]')
.should('be.disabled') .should('be.disabled')
cy.get('.new-post').find('[contenteditable]').type('Hello world') cy.get('.new-post').find('[contenteditable]').type('Hello world')
cy.get('.new-post input[type=submit]') cy.get('.new-post button[type=submit]')
.should('not.be.disabled') .should('not.be.disabled')
cy.get('.new-post input[type=submit]') cy.get('.new-post button[type=submit]')
.click() .click()
cy.wait('@postMessage') cy.wait('@postMessage')
cy.get('.social__timeline div.timeline-entry:first-child').should('contain', 'Hello world') cy.get('.social__timeline div.timeline-entry:first-child').should('contain', 'Hello world')
}) })
it('No longer see the empty content illustration', function() { it('No longer see the empty content illustration', function() {
cy.get('.emptycontent').should('not.be.visible') cy.get('.emptycontent').should('not.exist')
}) })
it('Write a post to followers with shift enter', function() { it('Write a post to followers with shift enter', function() {
@ -78,11 +76,11 @@ describe('Create posts', function() {
cy.server() cy.server()
cy.route('POST', '/index.php/apps/social/api/v1/post').as('postMessage') cy.route('POST', '/index.php/apps/social/api/v1/post').as('postMessage')
cy.route('GET', '/index.php/apps/social/api/v1/global/accounts/search') cy.route('GET', '/index.php/apps/social/api/v1/global/accounts/search')
cy.get('.new-post').find('[contenteditable]').type('@adm', {delay: 500}) cy.get('.new-post').find('[contenteditable]').type('@adm', { delay: 500 })
cy.get('.tribute-container').should('be.visible') cy.get('.tribute-container').should('be.visible')
cy.get('.tribute-container ul li:first').contains('admin') cy.get('.tribute-container ul li:first').contains('admin')
cy.get('.new-post').find('[contenteditable]').type('{enter} Hello there', {delay: 100, force: true}) cy.get('.new-post').find('[contenteditable]').type('{enter} Hello there', { delay: 100, force: true })
cy.get('.new-post input[type=submit]') cy.get('.new-post button[type=submit]')
.click() .click()
cy.wait('@postMessage') cy.wait('@postMessage')
cy.get('.social__timeline div.timeline-entry:first-child').should('contain', '@admin') cy.get('.social__timeline div.timeline-entry:first-child').should('contain', '@admin')
@ -93,9 +91,9 @@ describe('Create posts', function() {
cy.server() cy.server()
cy.route('POST', '/index.php/apps/social/api/v1/post').as('postMessage') cy.route('POST', '/index.php/apps/social/api/v1/post').as('postMessage')
cy.route('GET', '/index.php/apps/social/api/v1/global/accounts/search') cy.route('GET', '/index.php/apps/social/api/v1/global/accounts/search')
cy.get('.new-post').find('[contenteditable]').click({force: true}).type('@adm{enter} Hello world', {delay: 500, force: true}) cy.get('.new-post').find('[contenteditable]').click({ force: true }).type('@adm{enter} Hello world', { delay: 500, force: true })
cy.wait(500) cy.wait(500)
cy.get('.new-post input[type=submit]').should('not.be.disabled') cy.get('.new-post button[type=submit]').should('not.be.disabled')
const visibilityButton = cy.get('.new-post .options > div > button') const visibilityButton = cy.get('.new-post .options > div > button')
visibilityButton.should('have.class', 'icon-contacts-dark') visibilityButton.should('have.class', 'icon-contacts-dark')
@ -105,7 +103,7 @@ describe('Create posts', function() {
visibilityButton.click() visibilityButton.click()
cy.get('.new-post-form .popovermenu').should('not.be.visible') cy.get('.new-post-form .popovermenu').should('not.be.visible')
cy.get('.new-post input[type=submit]') cy.get('.new-post button[type=submit]')
.click() .click()
cy.wait('@postMessage') cy.wait('@postMessage')
cy.get('.social__timeline div.timeline-entry:first-child').should('contain', 'Hello world').should('contain', '@admin') cy.get('.social__timeline div.timeline-entry:first-child').should('contain', 'Hello world').should('contain', '@admin')

Wyświetl plik

@ -1,6 +1,8 @@
#!/usr/bin/env bash #!/usr/bin/env bash
echo "APP_NAME: $APP_NAME" echo "APP_NAME: $APP_NAME"
echo "BRANCH: $BRANCH"
chown -R www-data:www-data /var/www/html/data chown -R www-data:www-data /var/www/html/data
su www-data -c " su www-data -c "

Wyświetl plik

@ -1,20 +0,0 @@
// ***********************************************************
// 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)
const {
addMatchImageSnapshotPlugin
} = require('cypress-image-snapshot/plugin')
module.exports = (on, config) => {
addMatchImageSnapshotPlugin(on, config)
}

Wyświetl plik

@ -1,6 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# RUN THIS SCRIPT FROM THE ROOT FOLDER OF YOUR APP # RUN THIS SCRIPT FROM THE ROOT FOLDER OF YOUR APP
APP_NAME=${PWD##*/} APP_NAME=${PWD##*/}
CYPRESS_baseUrl=http://127.0.0.1:8082/index.php
if [[ $APP_NAME == "cypress" ]] if [[ $APP_NAME == "cypress" ]]
then then
@ -8,13 +9,11 @@ then
else else
echo "Launching docker server for the $APP_NAME app" echo "Launching docker server for the $APP_NAME app"
cd cypress cd cypress
docker-compose up -d docker-compose pull
echo -n "Waiting for server start " docker-compose up -d --force-recreate
until [[ $(docker-compose exec -u www-data -T nextcloud php ./occ status --output=json) == *"\"installed\":true"* ]] npm run wait-on $CYPRESS_baseUrl
do echo "Nextcloud successfully installed"
echo -n "."
done
echo ""
docker-compose exec --env APP_NAME=$APP_NAME -T nextcloud bash /initserver.sh docker-compose exec --env APP_NAME=$APP_NAME -T nextcloud bash /initserver.sh
docker-compose exec -u www-data -T nextcloud php ./occ social:reset -n docker-compose exec -u www-data -T nextcloud php ./occ social:reset -n
echo "Nextcloud successfully configured"
fi fi

Wyświetl plik

@ -20,37 +20,45 @@
* *
*/ */
import { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
addMatchImageSnapshotCommand()
const url = Cypress.config('baseUrl').replace(/\/index.php\/?$/g, '') const url = Cypress.config('baseUrl').replace(/\/index.php\/?$/g, '')
Cypress.env('baseUrl', url) Cypress.env('baseUrl', url)
Cypress.Commands.add('login', (user, password, route = '/apps/files') => { Cypress.Commands.add('login', (user, password, route = '/apps/files') => {
cy.clearCookies(); Cypress.Cookies.defaults({
preserve: /^(oc|nc)/,
})
cy.visit(route) cy.visit(route)
cy.get('input[name=user]').type(user) cy.get('input[name=user]').type(user)
cy.get('input[name=password]').type(password) cy.get('input[name=password]').type(password)
cy.get('#submit-wrapper input[type=submit]').click() cy.get('form[name=login] [type=submit]').click()
cy.url().should('include', route) cy.url().should('include', route)
}) })
Cypress.Commands.add('logout', () => { Cypress.Commands.add('logout', () => {
if (Cypress.$("input[name=user]").length > 0) { cy.getCookies()
// already logged out .then(cookies => {
} else { if (cookies.length === 0) {
cy.get('#expanddiv li[data-id="logout"] a').then(logout => { cy.log('Not logged, skipping logout...')
if (logout) { return
cy.visit(logout[0].href)
} }
return cy.get('body')
.then($body => {
const $settingsButton = $body.find('#settings #expand')
if ($settingsButton.length === 0) {
cy.log('Not logged in.')
return
}
$settingsButton.click()
cy.contains('Log out').click()
})
}) })
}
}) })
Cypress.Commands.add('nextcloudCreateUser', (user, password) => { Cypress.Commands.add('nextcloudCreateUser', (user, password) => {
cy.clearCookies();
cy.request({ cy.request({
method: 'POST', method: 'POST',
url: `${Cypress.env('baseUrl')}/ocs/v1.php/cloud/users?format=json`, url: `${Cypress.env('baseUrl')}/ocs/v1.php/cloud/users?format=json`,
@ -61,13 +69,12 @@ Cypress.Commands.add('nextcloudCreateUser', (user, password) => {
}, },
auth: { user: 'admin', pass: 'admin' }, auth: { user: 'admin', pass: 'admin' },
headers: { headers: {
'OCS-ApiRequest': 'true',
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${btoa('admin:admin')}`, 'OCS-ApiRequest': 'true',
Authorization: `Basic ${Buffer.from('admin:admin').toString('base64')}`,
}, },
}).then(response => {
cy.log(`Created user ${user}`, response.status)
}) })
cy.clearCookies()
}) })
Cypress.Commands.add('uploadFile', (fileName, mimeType, path = '') => { Cypress.Commands.add('uploadFile', (fileName, mimeType, path = '') => {
@ -82,7 +89,7 @@ Cypress.Commands.add('uploadFile', (fileName, mimeType, path = '') => {
headers: { headers: {
requesttoken: window.OC.requestToken, requesttoken: window.OC.requestToken,
'Content-Type': mimeType, 'Content-Type': mimeType,
} },
}).then(response => { }).then(response => {
cy.log(`Uploaded ${fileName}`, response) cy.log(`Uploaded ${fileName}`, response)
}) })
@ -122,7 +129,7 @@ Cypress.Commands.add('deleteFile', fileName => {
* Create a share link and return the share url * Create a share link and return the share url
* *
* @param {string} path the file/folder path * @param {string} path the file/folder path
* @returns {string} the share link url * @return {string} the share link url
*/ */
Cypress.Commands.add('createLinkShare', path => { Cypress.Commands.add('createLinkShare', path => {
return cy.window().then(async window => { return cy.window().then(async window => {
@ -133,26 +140,15 @@ Cypress.Commands.add('createLinkShare', path => {
}, { }, {
headers: { headers: {
requesttoken: window.OC.requestToken, requesttoken: window.OC.requestToken,
} },
}) })
if (!('ocs' in request.data) || !('token' in request.data.ocs.data && request.data.ocs.data.token.length > 0)) { if (!('ocs' in request.data) || !('token' in request.data.ocs.data && request.data.ocs.data.token.length > 0)) {
throw request throw request
} }
cy.log('Share link created', request.data.ocs.data.token) cy.log('Share link created', request.data.ocs.data.token)
return cy.wrap(request.data.ocs.data.token) return cy.wrap(request.data.ocs.data.token)
} catch(error) { } catch (error) {
console.error(error) console.error(error)
} }
}).should('have.length', 15) }).should('have.length', 15)
}) })
Cypress.Commands.overwrite('matchImageSnapshot', (originalFn, subject, name, options) => {
// hide avatar because random colour break the visual regression tests
cy.window().then(window => {
const avatarDiv = window.document.querySelector('.avatardiv')
if (avatarDiv) {
avatarDiv.remove()
}
})
return originalFn(subject, name, options)
})

Wyświetl plik

@ -1,5 +1,5 @@
// *********************************************************** // ***********************************************************
// This example support/index.js is processed and // This example support/e2e.js is processed and
// loaded automatically before your test files. // loaded automatically before your test files.
// //
// This is a great place to put global configuration and // This is a great place to put global configuration and
@ -14,7 +14,4 @@
// *********************************************************** // ***********************************************************
// Import commands.js using ES2015 syntax: // Import commands.js using ES2015 syntax:
import './commands' import './commands.js'
// Alternatively you can use CommonJS syntax:
// require('./commands')

5680
package-lock.json wygenerowano

Plik diff jest za duży Load Diff

Wyświetl plik

@ -25,8 +25,9 @@
"lint:fix": "eslint --ext .js,.vue src --fix", "lint:fix": "eslint --ext .js,.vue src --fix",
"test": "jest", "test": "jest",
"test:coverage": "jest --coverage", "test:coverage": "jest --coverage",
"cypress": "cypress run", "cypress": "./cypress/start.sh; cypress run; ./cypress/stop.sh",
"cypress:gui": "cypress open" "cypress:gui": "./cypress/start.sh; cypress open; ./cypress/stop.sh",
"wait-on": "wait-on -i 500 -t 300000"
}, },
"dependencies": { "dependencies": {
"@nextcloud/auth": "^2.0.0", "@nextcloud/auth": "^2.0.0",
@ -72,13 +73,16 @@
"npm": "^7.0.0 || ^8.0.0" "npm": "^7.0.0 || ^8.0.0"
}, },
"devDependencies": { "devDependencies": {
"@cypress/browserify-preprocessor": "^3.0.2",
"@nextcloud/babel-config": "^1.0.0", "@nextcloud/babel-config": "^1.0.0",
"@nextcloud/browserslist-config": "^2.3.0", "@nextcloud/browserslist-config": "^2.3.0",
"@nextcloud/eslint-config": "^8.1.4", "@nextcloud/eslint-config": "^8.1.4",
"@nextcloud/webpack-vue-config": "^5.4.0", "@nextcloud/webpack-vue-config": "^5.4.0",
"cypress": "^11.2.0",
"jest": "^29.3.1", "jest": "^29.3.1",
"jest-serializer-vue": "^3.1.0", "jest-serializer-vue": "^3.1.0",
"vue-template-compiler": "^2.7.10" "vue-template-compiler": "^2.7.10",
"wait-on": "^7.0.1"
}, },
"jest": { "jest": {
"moduleFileExtensions": [ "moduleFileExtensions": [

Wyświetl plik

@ -120,6 +120,7 @@
<div class="emptySpace" /> <div class="emptySpace" />
<NcButton :value="currentVisibilityPostLabel" <NcButton :value="currentVisibilityPostLabel"
:disabled="!canPost" :disabled="!canPost"
native-type="submit"
type="primary" type="primary"
@click.prevent="createPost"> @click.prevent="createPost">
<template #icon> <template #icon>