diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e372bdbc..1602f9a83 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,8 +11,8 @@ jobs: name: CI strategy: matrix: - nextcloudVersion: [ stable22, stable23, stable24, stable25, master ] - phpVersion: [ 7.4, 8.0, 8.1 ] + nextcloudVersion: [ master ] + phpVersion: [ 7.4 ] exclude: - nextcloudVersion: stable22 phpVersion: 8.1 @@ -21,6 +21,27 @@ jobs: - nextcloudVersion: stable24 phpVersion: 8.1 runs-on: ubuntu-latest +# container: ubuntu:latest +# services: +# openproject: +# image: openproject/community:12 +# env: +# OPENPROJECT_SECRET_KEY_BASE: secret +## OPENPROJECT_HOST__NAME: localhost:8081 +# OPENPROJECT_HTTPS: false +# OPENPROJECT_PASSWORD__MIN__LENGTH: 0 +# OPENPROJECT_ONBOARDING__ENABLED: false +# OPENPROJECT_AUTHENTICATION_GLOBAL__BASIC__AUTH_USER: apiadmin +# OPENPROJECT_AUTHENTICATION_GLOBAL__BASIC__AUTH_PASSWORD: apiadmin + services: + postgres: + image: postgres + ports: + - 5432:5432 + env: + POSTGRES_PASSWORD: openproject-dev-password + POSTGRES_USER: openproject + POSTGRES_DB: openproject_dev steps: - name: Cancel previous runs uses: styfle/cancel-workflow-action@0.9.1 @@ -31,6 +52,8 @@ jobs: - name: Checkout uses: actions/checkout@v2 + - run: mkdir -p artifacts + - name: Setup PHP ${{ matrix.phpVersion }} uses: shivammathur/setup-php@v2 with: @@ -65,33 +88,52 @@ jobs: - name: Setup NPM ${{ steps.versions.outputs.npmVersion }} run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}" + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1.2' + bundler-cache: true + - run: | + ruby --version + - name: Install PHP Dependencies run: | + sudo apt-get install git composer install --no-progress --prefer-dist --optimize-autoloader git clone --depth 1 https://github.com/nextcloud/server.git -b ${{ matrix.nextcloudVersion }} cd server && git submodule update --init ./occ maintenance:install --admin-pass=admin + ./occ config:system:set allow_local_remote_servers --value 1 git clone --depth 1 https://github.com/nextcloud/notifications.git -b ${{ matrix.nextcloudVersion }} apps/notifications + pwd - - name: PHP stan - run: make phpstan + - name: setup openproject + run: | + mkdir ~/dev + cd ~/dev + pwd +# git clone https://github.com/opf/openproject.git +# cd openproject - - name: PHP code style - run: composer run cs:check || ( echo 'Please run `composer run cs:fix` to format your code' && exit 1 ) +# - name: PHP stan +# run: make phpstan + +# - name: PHP code style +# run: composer run cs:check || ( echo 'Please run `composer run cs:fix` to format your code' && exit 1 ) - name: Install NPM Dependencies run: npm install - - name: JS Lint - run: npm run lint - - - name: Style Lint - run: npm run stylelint - - - name: PHP & Vue Unit Tests - run: | - make phpunit - make jsunit +# - name: JS Lint +# run: npm run lint +# +# - name: Style Lint +# run: npm run stylelint +# +# - name: PHP & Vue Unit Tests +# run: | +# make phpunit +# make jsunit - name: API Tests env: @@ -105,7 +147,41 @@ jobs: ./occ a:e integration_openproject php -S localhost:8080 2> /dev/null & cd apps/integration_openproject - make api-test +# make api-test + +# - name: wait on OpenProject +# uses: iFaxity/wait-on-action@v1 +# with: +# resource: http-get://localhost/api/v3/ +# timeout: 240000 +# - name: try OpenProject +# run: curl http://localhost/api/v3/users -u apiadmin:apiadmin + + - name: try Nextcloud + run: | + curl http://localhost:8080/status.php + curl --silent 'http://localhost:8080/ocs/v2.php/cloud/capabilities?format=json'-H "OCS-APIREQUEST: 1" | json_pp + + - name: e2e test + id: e2e + run: | + cd server + ./occ config:system:set allow_local_remote_servers --value 1 + ./occ config:system:get allow_local_remote_servers + ./occ config:system:set csrf.disabled --value true + ./occ config:system:set overwrite.cli.url --value 'http://localhost:8080' + cat config/config.php + cd apps/integration_openproject + make + npm run test:e2e tests/e2e/features/webUI + continue-on-error: true + + - name: Upload tracing artifacts + if: steps.e2e.outcome == 'failure' + uses: actions/upload-artifact@v3 + with: + name: e2e-tracing + path: server/apps/integration_openproject/tests/e2e/report/ - name: JS Code Coverage Summary Report if: ${{ github.event_name == 'pull_request' && matrix.nextcloudVersion == 'master' && matrix.phpVersion == '7.4' }} diff --git a/cucumber.conf.js b/cucumber.conf.js new file mode 100644 index 000000000..d60d1996e --- /dev/null +++ b/cucumber.conf.js @@ -0,0 +1,46 @@ +const { Before, BeforeAll, AfterAll, After, setDefaultTimeout } = require("@cucumber/cucumber") +const { chromium } = require("playwright") +const {config} = require("./tests/e2e/config") +const apiHelper = require('./tests/e2e/helpers/apiHelper') +const { OpenprojectAdminPage } = require("./tests/e2e/pageObjects/OpenprojectAdminPage") +const opAdminPageObject = new OpenprojectAdminPage() + +setDefaultTimeout(120000) + +BeforeAll(async function () { + await apiHelper.createAdmin() + global.browserNC = await chromium.launch({ + headless: true, + }); + global.browserOP = await chromium.launch({ + headless: true, + }); +}); + +AfterAll(async function () { + await global.browserNC.close() + await global.browserOP.close() +}); + +Before(async function () { + global.contextNC = await global.browserNC.newContext() + await contextNC.grantPermissions(['clipboard-read','clipboard-write']); + await contextNC.tracing.start({ screenshots: true, snapshots: true }); + global.pageNC = await global.contextNC.newPage() + global.contextOP = await global.browserOP.newContext() + await contextOP.grantPermissions(['clipboard-read','clipboard-write']); + await contextOP.tracing.start({ screenshots: true, snapshots: true }); + global.pageOP = await global.contextOP.newPage() +}); + +After(async function () { + await apiHelper.resetNextcloudOauthSettings() + // await opAdminPageObject.deleteFileStorage() + await global.pageNC.close(); + await contextNC.tracing.stop({ path: 'tests/e2e/report/traceNC.zip' }); + await global.contextNC.close(); + await global.pageOP.close(); + await contextOP.tracing.stop({ path: 'tests/e2e/report/traceOP.zip' }); + await global.contextOP.close(); + +}); diff --git a/lib/Controller/ConfigController.php b/lib/Controller/ConfigController.php index 036fffa13..01d46e18f 100755 --- a/lib/Controller/ConfigController.php +++ b/lib/Controller/ConfigController.php @@ -132,6 +132,7 @@ public function setConfig(array $values): DataResponse { /** * set admin config values + * @NoCSRFRequired * * @param array $values * diff --git a/package.json b/package.json index 1e96615c9..008d447ce 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "stylelint": "stylelint css src", "stylelint:fix": "stylelint css src --fix", "test:unit": "jest --silent", - "test:unit:watch": "jest --watch --no-coverage" + "test:unit:watch": "jest --watch --no-coverage", + "test:e2e": "cucumber-js --require cucumber.conf.js --require tests/e2e/stepDefinitions/**/*.js --format @cucumber/pretty-formatter" }, "repository": { "type": "git", @@ -52,15 +53,20 @@ "vue-material-design-icons": "^5.0.0" }, "devDependencies": { + "@cucumber/cucumber": "^8.7.0", + "@cucumber/pretty-formatter": "^1.0.0", "@nextcloud/babel-config": "^1.0.0", "@nextcloud/browserslist-config": "^2.2.0", "@nextcloud/eslint-config": "^8.0.0", "@nextcloud/stylelint-config": "^2.1.2", "@nextcloud/webpack-vue-config": "^5.0.0", + "@playwright/test": "^1.27.1", "@vue/cli-plugin-unit-jest": "~5.0.0", "@vue/test-utils": "^1.3.0", "@vue/vue2-jest": "^27.0.0", "eslint-webpack-plugin": "^3.1.1", + "node-fetch": "^2.6.7", + "playwright": "^1.27.1", "stylelint-webpack-plugin": "^3.3.0" } } diff --git a/report/screenshot.png b/report/screenshot.png new file mode 100644 index 000000000..51ed3c346 Binary files /dev/null and b/report/screenshot.png differ diff --git a/tests/e2e/config.js b/tests/e2e/config.js new file mode 100644 index 000000000..876d15067 --- /dev/null +++ b/tests/e2e/config.js @@ -0,0 +1,9 @@ + +exports.config = { + baseUrlNC: process.env.NEXTCLOUD_HOST || 'http://localhost:8080', + baseUrlOP: process.env.OPENPROJECT_HOST || 'http://localhost:8081', + openprojectBasicAuthUser: process.env.OPENPROJECT_AUTHENTICATION_GLOBAL__BASIC__AUTH_USER || 'apiadmin', + openprojectBasicAuthPass: process.env.OPENPROJECT_AUTHENTICATION_GLOBAL__BASIC__AUTH_PASSWORD || 'apiadmin', + headless: process.env.HEADLESS === 'true', + +} diff --git a/tests/e2e/features/webUI/OauthFlow.feature b/tests/e2e/features/webUI/OauthFlow.feature new file mode 100644 index 000000000..2ba64871b --- /dev/null +++ b/tests/e2e/features/webUI/OauthFlow.feature @@ -0,0 +1,21 @@ +Feature: + As an admin + I want to setup oauth between nextcloud and openproject + So that the apps can communicate with each other + + + Scenario: user logs in + Given openproject administrator has logged in openproject using the webUI + And nextcloud administrator has logged in using the webUI + And the administrator has navigated to the openproject tab in administrator settings + And nextcloud administrator adds the openproject host + When openproject administrator adds the nextcloud host with name "nextcloud" in file storage + And openproject administrator copies the openproject oauth credentials + And nextcloud administrator pastes the openproject oauth credentials + And nextcloud administrator copies the nextcloud oauth credentials + And openproject administrator pastes the nextcloud oauth credentials + Then file storage "nextcloud" should be listed on the webUI of openproject + And the oauth setting from should be completed on the webUI of nextcloud + When administator connects to the openproject through the personal settings + And the user authorizes in open project with username "admin2" and password "admin2" + Then the button with "Disconnect from OpenProject" text should be displayed in the webUI diff --git a/tests/e2e/helpers/apiHelper.js b/tests/e2e/helpers/apiHelper.js new file mode 100644 index 000000000..8593bb301 --- /dev/null +++ b/tests/e2e/helpers/apiHelper.js @@ -0,0 +1,61 @@ +const fetch = require('node-fetch') +const { config } = require('../config') +const { throwError } = require('@vue/vue2-jest/lib/utils') + +const createAdmin = function() { + const url = config.baseUrlOP + '/api/v3/users' + const data = { + login: 'admin2', + password: 'admin2', + firstName: 'Second', + lastName: 'Admin', + email: 'admin@mail.com', + admin: true, + status: 'active', + language: 'en', + } + fetch(url, { + method: 'POST', + body: JSON.stringify(data), + headers: { + Authorization: 'Basic ' + Buffer.from(config.openprojectBasicAuthUser + ':' + config.openprojectBasicAuthPass).toString('base64'), + 'Content-Type': 'application/json', + }, + }).then(function(response) { + if (response.status !== 201) { + throwError('Cannot create the admin user') + } + }).catch(function(error) { + throwError('Cannot create the admin user' + error) + }) +} + +const resetNextcloudOauthSettings = function() { + const url = config.baseUrlNC + '/index.php/apps/integration_openproject/admin-config' + const data = { + values: + { + client_id: null, + client_secret: null, + default_enable_navigation: false, + default_enable_notifications: false, + default_enable_unified_search: false, + oauth_instance_url: null, + }, + } + fetch(url, { + method: 'PUT', + body: JSON.stringify(data), + headers: { + Authorization: 'Basic ' + Buffer.from('admin' + ':' + 'admin').toString('base64'), + 'Content-Type': 'application/json; charset=utf-8', + }, + }).then(function(response) { + if (response.status !== 200) { + throwError('Error while resetting nextcloud oauth setup') + } + }).catch(function(error) { + throwError('Cannot reset the nextcloud settings' + error) + }) +} +module.exports = { createAdmin, resetNextcloudOauthSettings } diff --git a/tests/e2e/pageObjects/NextcloudAdminPage.js b/tests/e2e/pageObjects/NextcloudAdminPage.js new file mode 100644 index 000000000..44762601a --- /dev/null +++ b/tests/e2e/pageObjects/NextcloudAdminPage.js @@ -0,0 +1,86 @@ +/* global pageNC */ +const { expect } = require('@playwright/test') +const { config } = require('../config') + +class NextcloudAdminPage { + + constructor() { + this.settingsMenuSelector = '//div[@id="settings"]/div[@class="menutoggle"]' + this.settingsMenuOpenSelector = 'div#settings.openedMenu' + this.adminSettingSelector = '//li[@data-id="admin_settings"]' + this.openProjectTabSelector = '//li//a//span[text()="OpenProject"]' + this.openProjectOauthInstanceInputFieldSelector = '//div[@id="openproject-oauth-instance"]//div[@class="text-input-input-wrapper"]//input' + this.saveOauthInstanceButtonSelector = '[data-test-id="submit-server-host-form-btn"]' + this.openProjectOauthClientIdSelector = '//div[@id="openproject-oauth-client-id"]//div[@class="text-input-input-wrapper"]//input' + this.openProjectOauthSecretSelector = '//div[@id="openproject-oauth-client-secret"]//div[@class="text-input-input-wrapper"]//input' + this.submitOPOauthButtonSelector = '[data-test-id="submit-op-oauth-btn"]' + this.copyNCOauthClientIdButtonSelector = '//div[@id="nextcloud-oauth-client-id"]//button' + this.copyNCOauthSecretdButtonSelector = '//div[@id="nextcloud-oauth-client-secret"]//button' + this.submitNCOauthButtonSelector = '[data-test-id="submit-nc-oauth-values-form-btn"]' + this.resetAllAppSettingsSelector = '#reset-all-app-settings-btn' + this.resetConfirmSelector = '//div[@class="oc-dialog"]//div[contains(@class,"oc-dialog-buttonrow")]//button[text() = "Yes, reset"]' + this.defaultPreferenceSelector = '//div[@class="default-prefs"]' + this.errorMessage='.text-input-error-message' + } + + async adminNavigatesToAdminOPTab() { + await pageNC.goto(config.baseUrlNC + '/index.php/settings/admin/openproject') + await pageNC.waitForSelector('#openproject_prefs',10000) + // await pageNC.waitForTimeout(10000) + // await pageNC.waitForSelector(this.settingsMenuSelector) + // await pageNC.locator(this.settingsMenuSelector).click() + // await pageNC.waitForSelector(this.settingsMenuOpenSelector) + // await pageNC.locator(this.adminSettingSelector).click() + // await pageNC.waitForSelector('#security-warning',{state:"visible", timeout:10000}) + // await pageNC.waitForSelector(this.openProjectTabSelector,{state:"visible", timeout:10000}) + // await Promise.all([ + // // Waits for the next navigation. + // // It is important to call waitForNavigation before click to set up waiting. + // pageNC.waitForNavigation(), + // // Triggers a navigation after a timeout. + // pageNC.locator(this.openProjectTabSelector).last().click() + // ]); + } + + async adminAddsOpenProjectHost() { + await pageNC.waitForTimeout(20000) + await pageNC.waitForSelector('#app-content',10000) + await pageNC.waitForSelector('#openproject_prefs',10000) + await pageNC.waitForSelector('//h2[@class="settings-title"]//span[text()="OpenProject integration"]',{state:"visible",timeout:50000}) + await pageNC.waitForSelector(this.openProjectOauthInstanceInputFieldSelector,{state:"visible",timeout:60000}) + await pageNC.click(this.openProjectOauthInstanceInputFieldSelector) + await pageNC.fill(this.openProjectOauthInstanceInputFieldSelector, config.baseUrlOP) + await pageNC.click(this.saveOauthInstanceButtonSelector) + await expect(pageNC.locator(this.errorMessage)).not.toBeVisible() + await pageNC.waitForSelector(this.openProjectOauthClientIdSelector) + } + + async adminSetsTheOpOauthCreds(opClientId, opClientSecret) { + await pageNC.click(this.openProjectOauthClientIdSelector) + await pageNC.fill(this.openProjectOauthClientIdSelector, opClientId) + await pageNC.click(this.openProjectOauthSecretSelector) + await pageNC.fill(this.openProjectOauthSecretSelector, opClientSecret) + await pageNC.click(this.submitOPOauthButtonSelector) + } + + async adminCopiesTheNcOauthCreds() { + await pageNC.click(this.copyNCOauthClientIdButtonSelector) + const nextcloudClientId = await pageNC.evaluate(() => navigator.clipboard.readText()) + await pageNC.click(this.copyNCOauthSecretdButtonSelector) + const nextcloudClientSecret = await pageNC.evaluate(() => navigator.clipboard.readText()) + await pageNC.click(this.submitNCOauthButtonSelector) + return { client_id: nextcloudClientId, client_secret: nextcloudClientSecret } + } + + async isDefaultPrefsVisible() { + await expect(pageNC.locator(this.defaultPreferenceSelector)).toBeVisible() + } + + async resetNCOauthSetUP() { + await pageNC.click(this.resetAllAppSettingsSelector) + await pageNC.click(this.resetConfirmSelector) + } + +} + +module.exports = { NextcloudAdminPage } diff --git a/tests/e2e/pageObjects/NextcloudLoginPage.js b/tests/e2e/pageObjects/NextcloudLoginPage.js new file mode 100644 index 000000000..8658aa414 --- /dev/null +++ b/tests/e2e/pageObjects/NextcloudLoginPage.js @@ -0,0 +1,21 @@ +/* global pageNC */ +const { config } = require('../config') + +class NextcloudLoginPage { + + constructor() { + this.usernameSelector = '#user' + this.passwordSelector = '#password' + this.submitButtonSelector = '//button[@type="submit"]' + } + + async userLogsInNextcloud(username, password) { + await pageNC.goto(config.baseUrlNC) + await pageNC.fill(this.usernameSelector, username) + await pageNC.fill(this.passwordSelector, password) + await pageNC.click(this.submitButtonSelector) + await pageNC.waitForSelector('.app-dashboard') + } + +} +module.exports = { NextcloudLoginPage } diff --git a/tests/e2e/pageObjects/NextcloudPersonalSettingPage.js b/tests/e2e/pageObjects/NextcloudPersonalSettingPage.js new file mode 100644 index 000000000..4fc958819 --- /dev/null +++ b/tests/e2e/pageObjects/NextcloudPersonalSettingPage.js @@ -0,0 +1,29 @@ +/* global pageNC */ + +class NextcloudPersonalSettingPage { + + constructor() { + this.openProjectTabSelector = '//li//a//span[text()="OpenProject"]' + this.oauthConnectButtonSelector = '//button[contains(@class,"oauth-connect--button")]' + this.opAuthorizeButtonSelector = '//input[@value="Authorize"]' + this.openProjectDisconnectButtonSelector = '//button[contains(@class,"openproject-prefs--disconnect")]//span//span[@class="button-vue__text"]' + } + + async connectToOpenProjectParsonalSettings() { + await pageNC.locator(this.openProjectTabSelector).first().click() + await pageNC.click(this.oauthConnectButtonSelector) + + } + + async authorizeApiOP() { + await pageNC.click(this.opAuthorizeButtonSelector) + } + + async isConnectedToOpenProject() { + const text = await pageNC.locator(this.openProjectDisconnectButtonSelector).textContent() + return text.trim() + } + +} + +module.exports = { NextcloudPersonalSettingPage } diff --git a/tests/e2e/pageObjects/OpenprojectAdminPage.js b/tests/e2e/pageObjects/OpenprojectAdminPage.js new file mode 100644 index 000000000..6e1544b39 --- /dev/null +++ b/tests/e2e/pageObjects/OpenprojectAdminPage.js @@ -0,0 +1,70 @@ +/* global pageOP */ +const { expect } = require('@playwright/test') +const { config } = require('../config') + +class OpenprojectAdminPage { + + constructor() { + this.openProjectAvatarSelector = '//div[@title="Second Admin"]' + this.administratorSettingMenuItemSelector = '//a[contains(@class,"administration-menu-item ")]' + this.fileStoragesSelector = '//a[@title="File storages"]' + this.addNewStoragesSelector = '//a[@class="wp-inline-create--add-link"]' + this.storageNameInputFieldSelector = '#storages_storage_name' + this.hostUrlInputFieldSelector = '#storages_storage_host' + this.continueSetupButtonSelector = '//button[text() = "Save and continue setup"]' + this.copyClientIdButtonSelector = '//button[contains(@class,"client-id-copy-button")]' + this.copyClientSecretButtonSelector = '//button[contains(@class,"secret-copy-button")]' + this.doneContinueSetupButtonSelector = '//a[text() = "Done. Continue setup"]' + this.oauthClientIdInputFieldSelectorOP = '#oauth_client_client_id' + this.oauthClientSecretInputFieldSelectorOP = '#oauth_client_client_secret' + this.saveAndCompleteSetupButtonSelector = '//button[text() = "Save and complete setup"]' + this.deleteFileStorageSelector = '//li[@class="toolbar-item"]/a//span[text()="Delete"]' + this.fileStorageBreadcrumbSelector = '//div[@id="breadcrumb"]//li/a[text()="File storages"]' + this.fileStorageNameSelector = '//td[@class="name"]/a' + this.skipHintSelector = "//div[contains(@class,'enjoyhint_btn-transparent') and (text() = 'Skip')]" + } + + async adminAddsFileStorageHost(name) { + await pageOP.click(this.openProjectAvatarSelector) + await pageOP.click(this.administratorSettingMenuItemSelector) + await pageOP.click(this.skipHintSelector) + await pageOP.click(this.fileStoragesSelector) + await pageOP.click(this.addNewStoragesSelector) + await pageOP.fill(this.storageNameInputFieldSelector, name) + await pageOP.fill(this.hostUrlInputFieldSelector, config.baseUrlNC) + await pageOP.click(this.continueSetupButtonSelector) + await pageOP.waitForSelector(this.copyClientIdButtonSelector) + } + + async copyOpenProjectOauthCreds() { + await pageOP.click(this.copyClientIdButtonSelector) + const openProjectClientId = await pageOP.evaluate(() => navigator.clipboard.readText()) + await pageOP.click(this.copyClientSecretButtonSelector) + const openProjectClientSecret = await pageOP.evaluate(() => navigator.clipboard.readText()) + await pageOP.click(this.doneContinueSetupButtonSelector) + await pageOP.waitForSelector(this.oauthClientIdInputFieldSelectorOP) + return { client_id: openProjectClientId, client_secret: openProjectClientSecret } + } + + async pasteNCOauthCreds(ncClientId, ncClientSecret) { + await pageOP.fill(this.oauthClientIdInputFieldSelectorOP, ncClientId) + await pageOP.fill(this.oauthClientSecretInputFieldSelectorOP, ncClientSecret) + await pageOP.click(this.saveAndCompleteSetupButtonSelector) + } + + async fileStorageShouldBeVisible(name) { + await pageOP.click(this.fileStorageBreadcrumbSelector) + await expect(pageOP.locator(this.fileStorageNameSelector)).toHaveText(name) + } + + async deleteFileStorage() { + await pageOP.locator(this.fileStorageNameSelector).click() + await pageOP.locator(this.deleteFileStorageSelector).click() + await pageOP.on('dialog', async (dialog) => { + dialog.accept() + }) + } + +} + +module.exports = { OpenprojectAdminPage } diff --git a/tests/e2e/pageObjects/OpenprojectLoginPage.js b/tests/e2e/pageObjects/OpenprojectLoginPage.js new file mode 100644 index 000000000..627a6b5d9 --- /dev/null +++ b/tests/e2e/pageObjects/OpenprojectLoginPage.js @@ -0,0 +1,33 @@ +/* global pageOP , pageNC */ +const { config } = require('../config') + +class OpenprojectLoginPage { + + constructor() { + this.openProjectTitle = '//a[@title="Sign in"]' + this.usernameSelector = '#username-pulldown' + this.passwordSelector = '#password-pulldown' + this.userSignUP = '#login-pulldown' + this.quickAddMenuSelector = '.op-quick-add-menu--icon' + } + + async userLogsInOpenproject(username, password) { + await pageOP.goto(config.baseUrlOP) + await this.fillUpLoginForm(username, password) + await pageOP.waitForSelector(this.quickAddMenuSelector) + } + + async fillUpLoginForm(username, password, nextcloud) { + let page = null + if (nextcloud) { + page = pageNC + } else page = pageOP + await page.click(this.openProjectTitle) + await page.fill(this.usernameSelector, username) + await page.fill(this.passwordSelector, password) + await page.click(this.userSignUP) + } + +} + +module.exports = { OpenprojectLoginPage } diff --git a/tests/e2e/stepDefinitions/administrationContext.js b/tests/e2e/stepDefinitions/administrationContext.js new file mode 100644 index 000000000..f5d2d0723 --- /dev/null +++ b/tests/e2e/stepDefinitions/administrationContext.js @@ -0,0 +1,56 @@ +const { Given, When, Then } = require('@cucumber/cucumber') + +const { NextcloudAdminPage } = require('../pageObjects/NextcloudAdminPage') +const { OpenprojectAdminPage } = require('../pageObjects/OpenprojectAdminPage') + +const ncAdminPageObject = new NextcloudAdminPage() +const opAdminPageObject = new OpenprojectAdminPage() + +let opClientId = '' +let opClientSecret = '' +let ncClientId = '' +let ncClientSecret = '' + +Given('the administrator has navigated to the openproject tab in administrator settings', async function() { + await ncAdminPageObject.adminNavigatesToAdminOPTab() +}) + +When('openproject administrator adds the nextcloud host with name {string} in file storage', async function(name) { + await opAdminPageObject.adminAddsFileStorageHost(name) +}) + +When('nextcloud administrator adds the openproject host', async function() { + await ncAdminPageObject.adminAddsOpenProjectHost() +}) + +When('openproject administrator copies the openproject oauth credentials', async function() { + const values = await opAdminPageObject.copyOpenProjectOauthCreds() + opClientId = values.client_id + opClientSecret = values.client_secret +}) + +When('nextcloud administrator pastes the openproject oauth credentials', async function() { + await ncAdminPageObject.adminSetsTheOpOauthCreds(opClientId, opClientSecret) + opClientId = '' + opClientSecret = '' +}) + +When('nextcloud administrator copies the nextcloud oauth credentials', async function() { + const values = await ncAdminPageObject.adminCopiesTheNcOauthCreds() + ncClientId = values.client_id + ncClientSecret = values.client_secret +}) + +When('openproject administrator pastes the nextcloud oauth credentials', async function() { + await opAdminPageObject.pasteNCOauthCreds(ncClientId, ncClientSecret) + ncClientSecret = '' + ncClientId = '' +}) + +Then('file storage {string} should be listed on the webUI of openproject', async function(name) { + await opAdminPageObject.fileStorageShouldBeVisible(name) +}) + +Then('the oauth setting from should be completed on the webUI of nextcloud', async function() { + await ncAdminPageObject.isDefaultPrefsVisible() +}) diff --git a/tests/e2e/stepDefinitions/loginContext.js b/tests/e2e/stepDefinitions/loginContext.js new file mode 100644 index 000000000..47e9d42c0 --- /dev/null +++ b/tests/e2e/stepDefinitions/loginContext.js @@ -0,0 +1,22 @@ +const { Given, When } = require('@cucumber/cucumber') + +const { NextcloudLoginPage } = require('../pageObjects/NextcloudLoginPage') +const { OpenprojectLoginPage } = require('../pageObjects/OpenprojectLoginPage') +const { NextcloudPersonalSettingPage } = require('../pageObjects/NextcloudPersonalSettingPage') + +const ncLoginPageObject = new NextcloudLoginPage() +const opLoginPageObject = new OpenprojectLoginPage() +const ncPersonalSettingsPageObject = new NextcloudPersonalSettingPage() + +Given('nextcloud administrator has logged in using the webUI', async function() { + await ncLoginPageObject.userLogsInNextcloud('admin', 'admin') +}) + +Given('openproject administrator has logged in openproject using the webUI', async function() { + await opLoginPageObject.userLogsInOpenproject('admin2', 'admin2') +}) + +When('the user authorizes in open project with username {string} and password {string}', async function(username, password) { + await opLoginPageObject.fillUpLoginForm(username, password, true) + await ncPersonalSettingsPageObject.authorizeApiOP() +}) diff --git a/tests/e2e/stepDefinitions/personalSettingsContext.js b/tests/e2e/stepDefinitions/personalSettingsContext.js new file mode 100644 index 000000000..e0c99db41 --- /dev/null +++ b/tests/e2e/stepDefinitions/personalSettingsContext.js @@ -0,0 +1,15 @@ +const { When, Then } = require('@cucumber/cucumber') +const { expect } = require('@playwright/test') + +const { NextcloudPersonalSettingPage } = require('../pageObjects/NextcloudPersonalSettingPage') + +const ncPersonalSettingsPage = new NextcloudPersonalSettingPage() + +When('administator connects to the openproject through the personal settings', async function() { + await ncPersonalSettingsPage.connectToOpenProjectParsonalSettings() +}) + +Then('the button with {string} text should be displayed in the webUI', async function(expectedMessage) { + const actualMessage = await ncPersonalSettingsPage.isConnectedToOpenProject() + await expect(expectedMessage).toBe(actualMessage) +}) diff --git a/tests/op-database.yml b/tests/op-database.yml new file mode 100644 index 000000000..ddf4f3d66 --- /dev/null +++ b/tests/op-database.yml @@ -0,0 +1,15 @@ +#database.yml +default: &default + adapter: postgresql + encoding: unicode + host: localhost:5432 + username: openproject + password: openproject-dev-password + +development: + <<: *default + database: openproject_dev + +test: + <<: *default + database: openproject_test