@open-xchange/appsuite-codeceptjs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.defaults +47 -0
- package/README.md +40 -0
- package/chai.d.ts +5 -0
- package/customRerun.js +135 -0
- package/global.d.ts +5 -0
- package/index.js +187 -0
- package/package.json +39 -0
- package/src/actor.js +174 -0
- package/src/appsuiteHttpClient.js +155 -0
- package/src/chai.d.ts +6 -0
- package/src/chai.js +58 -0
- package/src/contexts/contexts.js +172 -0
- package/src/contexts/reseller.js +248 -0
- package/src/contexts.js +29 -0
- package/src/event.js +54 -0
- package/src/helper.js +817 -0
- package/src/pageobjects/calendar.js +226 -0
- package/src/pageobjects/contacts.js +148 -0
- package/src/pageobjects/drive.js +96 -0
- package/src/pageobjects/fragments/contact-autocomplete.js +45 -0
- package/src/pageobjects/fragments/contact-picker.js +50 -0
- package/src/pageobjects/fragments/dialogs.js +41 -0
- package/src/pageobjects/fragments/search.js +54 -0
- package/src/pageobjects/fragments/settings-mailfilter.js +90 -0
- package/src/pageobjects/fragments/settings.js +71 -0
- package/src/pageobjects/fragments/tinymce.js +41 -0
- package/src/pageobjects/fragments/topbar.js +43 -0
- package/src/pageobjects/fragments/viewer.js +67 -0
- package/src/pageobjects/mail.js +67 -0
- package/src/pageobjects/mobile/mobileCalendar.js +41 -0
- package/src/pageobjects/mobile/mobileContacts.js +40 -0
- package/src/pageobjects/mobile/mobileMail.js +51 -0
- package/src/pageobjects/tasks.js +58 -0
- package/src/plugins/emptyModule/index.js +21 -0
- package/src/plugins/settingsInit/index.js +35 -0
- package/src/plugins/testmetrics/index.js +135 -0
- package/src/soap/services/context.js +147 -0
- package/src/soap/services/oxaas.js +36 -0
- package/src/soap/services/resellerContext.js +65 -0
- package/src/soap/services/resellerUser.js +100 -0
- package/src/soap/services/user.js +114 -0
- package/src/soap/services/util.js +39 -0
- package/src/soap/soap.js +172 -0
- package/src/users/reseller.js +233 -0
- package/src/users/users.js +183 -0
- package/src/users.js +29 -0
- package/src/util.js +104 -0
- package/steps.d.ts +16 -0
package/.env.defaults
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Default environment variables
|
|
2
|
+
#
|
|
3
|
+
# Do not edit this file, instead create a .env file in the root directory!
|
|
4
|
+
|
|
5
|
+
# CONTEXT_ID only needs to be set for local development
|
|
6
|
+
# CONTEXT_ID=
|
|
7
|
+
|
|
8
|
+
# Admin credentials for e2e middleware
|
|
9
|
+
# PROVISIONING_USER=
|
|
10
|
+
# PROVISIONING_PASSWORD=
|
|
11
|
+
|
|
12
|
+
# SMTP and IMAP server for e2e-tests
|
|
13
|
+
SMTP_SERVER=main-asbox-postfix
|
|
14
|
+
IMAP_SERVER=main-asbox-dovecot
|
|
15
|
+
|
|
16
|
+
# Number of maximum retries of e2e-test runs and minimal successful e2e-test runs
|
|
17
|
+
MIN_SUCCESS=1
|
|
18
|
+
MAX_RERUNS=1
|
|
19
|
+
|
|
20
|
+
# URL to the App Suite Middleware for e2e-provisioning via SOAP
|
|
21
|
+
PROVISIONING_URL=https://appsuite-main.dev.oxui.de/
|
|
22
|
+
|
|
23
|
+
# URL to the App Suite UI for e2e-tests
|
|
24
|
+
LAUNCH_URL=https://core-ui-main.dev.oxui.de
|
|
25
|
+
|
|
26
|
+
# MX domain for e2e-tests
|
|
27
|
+
MX_DOMAIN=box.ox.io
|
|
28
|
+
|
|
29
|
+
# API used for e2e user provisioning (common/reseller)
|
|
30
|
+
PROVISIONING_API=common
|
|
31
|
+
|
|
32
|
+
# Default timeout for all wait operations for e2e tests in ms
|
|
33
|
+
WAIT_TIMEOUT=5000
|
|
34
|
+
|
|
35
|
+
# Run test headless or in chrome (Must be true for CI)
|
|
36
|
+
HEADLESS=true
|
|
37
|
+
|
|
38
|
+
# Test metrics plugin configuration
|
|
39
|
+
E2E_TEST_METRICS_CLIENT=
|
|
40
|
+
E2E_TEST_METRICS_TOKEN=
|
|
41
|
+
|
|
42
|
+
# Path to the chrome binary
|
|
43
|
+
CHROME_BIN=/Applications/Google Chrome.app/Contents/MacOS/Google Chrome
|
|
44
|
+
|
|
45
|
+
# See https://peter.sh/experiments/chromium-command-line-switches
|
|
46
|
+
CHROME_ARGS=
|
|
47
|
+
# CHROME_ARGS: '--disable-accelerated-2d-canvas --no-zygote --single-process'
|
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Open-Xchange App Suite: CodeceptJS
|
|
2
|
+
|
|
3
|
+
This module contains appsuite specific configuration, helpers, page objects and actors for Open-Xchange App Suite.
|
|
4
|
+
|
|
5
|
+
## Configuration
|
|
6
|
+
|
|
7
|
+
Basic config
|
|
8
|
+
|
|
9
|
+
// inside codecept.conf.js
|
|
10
|
+
|
|
11
|
+
module.exports.config = {
|
|
12
|
+
'helpers': {
|
|
13
|
+
// other helpers...
|
|
14
|
+
AppSuite: {
|
|
15
|
+
require: './node_modules/@open-xchange/appsuite-codeceptjs/src/helper',
|
|
16
|
+
// AppSuite helpers and actors rely on users array provided via config
|
|
17
|
+
users: [{
|
|
18
|
+
username: 'testuser1',
|
|
19
|
+
password: 'ultimatelySecure',
|
|
20
|
+
mail: 'testuser1@example.com'
|
|
21
|
+
}]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
'include': {
|
|
25
|
+
'I': './node_modules/@open-xchange/appsuite-codeceptjs/src/actor'
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
If you want to have other actor functions bound to the I object, you can extend our actor in another file as follows:
|
|
30
|
+
|
|
31
|
+
// inside actor.js
|
|
32
|
+
|
|
33
|
+
module.exports = function () {
|
|
34
|
+
const {actor} = require('@open-xchange/appsuite-codeceptjs');
|
|
35
|
+
return actor({
|
|
36
|
+
customFunction: function () {
|
|
37
|
+
// do custom stuff
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
};
|
package/chai.d.ts
ADDED
package/customRerun.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com>
|
|
5
|
+
* @license AGPL-3.0
|
|
6
|
+
*
|
|
7
|
+
* This code is free software: you can redistribute it and/or modify
|
|
8
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
9
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
* (at your option) any later version.
|
|
11
|
+
*
|
|
12
|
+
* This program is distributed in the hope that it will be useful,
|
|
13
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
* GNU Affero General Public License for more details.
|
|
16
|
+
*
|
|
17
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
18
|
+
* along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
|
19
|
+
*
|
|
20
|
+
* Any use of the work other than as authorized under this license or copyright law is prohibited.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const { event, container, output, Codecept } = require('codeceptjs')
|
|
24
|
+
// @ts-ignore
|
|
25
|
+
const { getConfig, printError } = require('codeceptjs/lib/command/utils')
|
|
26
|
+
const fsPath = require('node:path')
|
|
27
|
+
class CodeceptRerunner extends Codecept {
|
|
28
|
+
constructor (config, options) {
|
|
29
|
+
super(config, options)
|
|
30
|
+
// @ts-ignore
|
|
31
|
+
this.configRerun = this.config.rerun || {}
|
|
32
|
+
this.minSuccess = this.configRerun.minSuccess || 1
|
|
33
|
+
this.maxReruns = this.configRerun.maxReruns || 1
|
|
34
|
+
this.successCounter = 0
|
|
35
|
+
this.rerunsCounter = 0
|
|
36
|
+
this.testSuccessCounter = {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async runOnce (test) {
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
container.createMocha()
|
|
42
|
+
const mocha = container.mocha()
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
this.testFiles.forEach((file) => {
|
|
45
|
+
delete require.cache[file]
|
|
46
|
+
})
|
|
47
|
+
// @ts-ignore
|
|
48
|
+
mocha.files = this.testFiles
|
|
49
|
+
if (test) {
|
|
50
|
+
if (!fsPath.isAbsolute(test)) {
|
|
51
|
+
// @ts-ignore
|
|
52
|
+
test = fsPath.join(codecept_dir, test)
|
|
53
|
+
}
|
|
54
|
+
mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test)
|
|
55
|
+
}
|
|
56
|
+
await new Promise(resolve => mocha.loadFiles(resolve))
|
|
57
|
+
mocha.suite.suites.forEach(suite => {
|
|
58
|
+
suite.tests = suite.tests.filter(test => {
|
|
59
|
+
return !this.lastRun ||
|
|
60
|
+
this.lastRun[test.fullTitle()] === 'failed' ||
|
|
61
|
+
(this.lastRun[test.fullTitle()] === 'passed' && this.testSuccessCounter[test.fullTitle()] < this.minSuccess)
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
const failures = await new Promise(resolve => mocha.run(resolve))
|
|
65
|
+
this.lastRun = {}
|
|
66
|
+
mocha.suite.suites.forEach((suite) => {
|
|
67
|
+
suite.tests.forEach(test => {
|
|
68
|
+
this.lastRun[test.fullTitle()] = (test?.state === 'passed' && 'passed') || 'failed'
|
|
69
|
+
if (test.state === 'passed') {
|
|
70
|
+
this.testSuccessCounter[test.fullTitle()] = this.testSuccessCounter[test.fullTitle()] ? this.testSuccessCounter[test.fullTitle()] + 1 : 1
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
if (failures > 0) {
|
|
75
|
+
throw new Error(`${failures} tests fail`)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async runTests (test) {
|
|
80
|
+
if (this.minSuccess > this.maxReruns) throw new Error('minSuccess must be less than maxReruns')
|
|
81
|
+
if (this.maxReruns === 1) {
|
|
82
|
+
await this.runOnce(test)
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
while (this.rerunsCounter < this.maxReruns && this.successCounter < this.minSuccess) {
|
|
86
|
+
this.rerunsCounter++
|
|
87
|
+
try {
|
|
88
|
+
await this.runOnce(test)
|
|
89
|
+
this.successCounter++
|
|
90
|
+
output.success(`\nProcess run ${this.rerunsCounter} of max ${this.maxReruns}, success runs ${this.successCounter}/${this.minSuccess}\n`)
|
|
91
|
+
} catch (e) {
|
|
92
|
+
output.error(`\nFail run ${this.rerunsCounter} of max ${this.maxReruns}, success runs ${this.successCounter}/${this.minSuccess} \n`)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (this.successCounter < this.minSuccess) {
|
|
96
|
+
throw new Error(`Flaky tests detected! ${this.successCounter} success runs achieved instead of ${this.minSuccess} success runs expected`)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async run (test) {
|
|
101
|
+
event.emit(event.all.before, this)
|
|
102
|
+
try {
|
|
103
|
+
await this.runTests(test)
|
|
104
|
+
} finally {
|
|
105
|
+
event.emit(event.all.result, this)
|
|
106
|
+
event.emit(event.all.after, this)
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
await this.teardown()
|
|
110
|
+
} catch (err) {
|
|
111
|
+
// silently ignore errors
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
;(async () => {
|
|
117
|
+
const codeceptDir = process.cwd()
|
|
118
|
+
const conf = getConfig(codeceptDir)
|
|
119
|
+
const [test] = process.argv.slice(2)
|
|
120
|
+
// parse --grep option in all variations
|
|
121
|
+
const fgrep = test === '--grep' ? process.argv[3] : (test || '').replace(/^--grep=?\s?/, '')
|
|
122
|
+
const customRunner = new CodeceptRerunner(conf, { fgrep, colors: true, reporter: 'mocha-multi' })
|
|
123
|
+
customRunner.init(codeceptDir)
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
await customRunner.bootstrap()
|
|
127
|
+
customRunner.loadTests()
|
|
128
|
+
// run tests
|
|
129
|
+
await customRunner.run()
|
|
130
|
+
process.exitCode = 0
|
|
131
|
+
} catch (err) {
|
|
132
|
+
printError(err)
|
|
133
|
+
process.exitCode = 1
|
|
134
|
+
}
|
|
135
|
+
})()
|
package/global.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com>
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
*
|
|
5
|
+
* This code is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
|
17
|
+
*
|
|
18
|
+
* Any use of the work other than as authorized under this license or copyright law is prohibited.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
require('@open-xchange/appsuite-codeceptjs/src/chai')
|
|
22
|
+
const dotenv = require('dotenv')
|
|
23
|
+
dotenv.config({ path: '.env' })
|
|
24
|
+
dotenv.config({ path: '.env.defaults' })
|
|
25
|
+
|
|
26
|
+
const requiredEnvVars = ['LAUNCH_URL', 'PROVISIONING_URL', 'CONTEXT_ID']
|
|
27
|
+
|
|
28
|
+
requiredEnvVars.forEach(function notdefined (key) {
|
|
29
|
+
if (process.env[key]) return
|
|
30
|
+
console.error('\x1b[31m', `ERROR: Missing value for environment variable '${key}'. Please specify a '.env' file analog to '.env-example'.`)
|
|
31
|
+
process.exit()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
util: require('./src/util'),
|
|
36
|
+
event: require('./src/event'),
|
|
37
|
+
config: {
|
|
38
|
+
tests: './tests/**/*_test.js',
|
|
39
|
+
timeout: Number(process.env.TEST_TIMEOUT) || 120,
|
|
40
|
+
output: './output/',
|
|
41
|
+
helpers: {
|
|
42
|
+
Playwright: {
|
|
43
|
+
timeout: 10000,
|
|
44
|
+
waitForTimeout: 10000,
|
|
45
|
+
browser: 'chromium',
|
|
46
|
+
chromium: {
|
|
47
|
+
args: [
|
|
48
|
+
'--disable-print-preview',
|
|
49
|
+
'--disable-crash-reporter',
|
|
50
|
+
'--disable-dev-shm-usage',
|
|
51
|
+
'--disable-features=IsolateOrigins',
|
|
52
|
+
'--disable-gpu',
|
|
53
|
+
'--disable-notifications', // to disable native notification window on Mac OS,
|
|
54
|
+
'--disable-print-preview',
|
|
55
|
+
'--disable-setuid-sandbox',
|
|
56
|
+
'--disable-site-isolation-trials',
|
|
57
|
+
'--disable-web-security',
|
|
58
|
+
'--no-first-run',
|
|
59
|
+
'--no-sandbox',
|
|
60
|
+
'--no-zygote'
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
url: process.env.LAUNCH_URL,
|
|
64
|
+
show: process.env.HEADLESS === 'false',
|
|
65
|
+
trace: process.env.TRACE === 'true',
|
|
66
|
+
restart: true // this is needed to be able to switch browsers in tests
|
|
67
|
+
},
|
|
68
|
+
AppSuite: {
|
|
69
|
+
require: '@open-xchange/appsuite-codeceptjs/src/helper',
|
|
70
|
+
mxDomain: process.env.MX_DOMAIN,
|
|
71
|
+
serverURL: process.env.PROVISIONING_URL,
|
|
72
|
+
contextId: process.env.CONTEXT_ID,
|
|
73
|
+
filestoreId: process.env.FILESTORE_ID,
|
|
74
|
+
smtpServer: process.env.SMTP_SERVER,
|
|
75
|
+
imapServer: process.env.IMAP_SERVER,
|
|
76
|
+
loginTimeout: 30,
|
|
77
|
+
admin: {
|
|
78
|
+
login: process.env.E2E_ADMIN_USER,
|
|
79
|
+
password: process.env.E2E_ADMIN_PW
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
include: {
|
|
84
|
+
I: '@open-xchange/appsuite-codeceptjs/src/actor',
|
|
85
|
+
users: '@open-xchange/appsuite-codeceptjs/src/users',
|
|
86
|
+
contexts: '@open-xchange/appsuite-codeceptjs/src/contexts',
|
|
87
|
+
// pageobjects
|
|
88
|
+
contacts: '@open-xchange/appsuite-codeceptjs/src/pageobjects/contacts',
|
|
89
|
+
calendar: '@open-xchange/appsuite-codeceptjs/src/pageobjects/calendar',
|
|
90
|
+
mail: '@open-xchange/appsuite-codeceptjs/src/pageobjects/mail',
|
|
91
|
+
drive: '@open-xchange/appsuite-codeceptjs/src/pageobjects/drive',
|
|
92
|
+
tasks: '@open-xchange/appsuite-codeceptjs/src/pageobjects/tasks',
|
|
93
|
+
// fragments
|
|
94
|
+
dialogs: '@open-xchange/appsuite-codeceptjs/src/pageobjects/fragments/dialogs',
|
|
95
|
+
autocomplete: '@open-xchange/appsuite-codeceptjs/src/pageobjects/fragments/contact-autocomplete',
|
|
96
|
+
contactpicker: '@open-xchange/appsuite-codeceptjs/src/pageobjects/fragments/contact-picker',
|
|
97
|
+
mailfilter: '@open-xchange/appsuite-codeceptjs/src/pageobjects/fragments/settings-mailfilter',
|
|
98
|
+
search: '@open-xchange/appsuite-codeceptjs/src/pageobjects/fragments/search',
|
|
99
|
+
tinymce: '@open-xchange/appsuite-codeceptjs/src/pageobjects/fragments/tinymce',
|
|
100
|
+
topbar: '@open-xchange/appsuite-codeceptjs/src/pageobjects/fragments/topbar',
|
|
101
|
+
settings: '@open-xchange/appsuite-codeceptjs/src/pageobjects/fragments/settings',
|
|
102
|
+
viewer: '@open-xchange/appsuite-codeceptjs/src/pageobjects/fragments/viewer',
|
|
103
|
+
mobileCalendar: '@open-xchange/appsuite-codeceptjs/src/pageobjects/mobile/mobileCalendar',
|
|
104
|
+
mobileMail: '@open-xchange/appsuite-codeceptjs/src/pageobjects/mobile/mobileMail',
|
|
105
|
+
mobileContacts: '@open-xchange/appsuite-codeceptjs/src/pageobjects/mobile/mobileContacts'
|
|
106
|
+
},
|
|
107
|
+
async bootstrap () {
|
|
108
|
+
// set moment defaults
|
|
109
|
+
// note: no need to require moment-timezone later on. requiring moment is enough
|
|
110
|
+
const moment = require('moment')
|
|
111
|
+
require('moment-timezone')
|
|
112
|
+
moment.tz.setDefault('Europe/Berlin')
|
|
113
|
+
|
|
114
|
+
const codecept = require('codeceptjs')
|
|
115
|
+
|
|
116
|
+
const config = codecept.config.get()
|
|
117
|
+
const helperConfig = config.helpers.AppSuite
|
|
118
|
+
|
|
119
|
+
const contexts = codecept.container.support('contexts')
|
|
120
|
+
|
|
121
|
+
const testRunContext = await contexts.create()
|
|
122
|
+
if (typeof testRunContext.id !== 'undefined') helperConfig.contextId = testRunContext.id
|
|
123
|
+
},
|
|
124
|
+
async teardown () {
|
|
125
|
+
const { contexts } = global.inject()
|
|
126
|
+
// we need to run this sequentially, less stress on the MW
|
|
127
|
+
for (const ctx of contexts.filter(ctx => ctx.id > 100)) {
|
|
128
|
+
if (ctx.id !== 10) await ctx.remove().catch(e => console.error(e.message))
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
reporter: 'mocha-multi',
|
|
132
|
+
mocha: {
|
|
133
|
+
reporterOptions: {
|
|
134
|
+
'codeceptjs-cli-reporter': {
|
|
135
|
+
stdout: '-'
|
|
136
|
+
},
|
|
137
|
+
'mocha-junit-reporter': {
|
|
138
|
+
stdout: '-',
|
|
139
|
+
options: {
|
|
140
|
+
jenkinsMode: true,
|
|
141
|
+
mochaFile: './output/junit.xml',
|
|
142
|
+
attachments: false // add screenshot for a failed test
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
plugins: {
|
|
148
|
+
allure: {
|
|
149
|
+
enabled: true,
|
|
150
|
+
require: 'allure-codeceptjs',
|
|
151
|
+
outputDir: './output'
|
|
152
|
+
},
|
|
153
|
+
testMetrics: {
|
|
154
|
+
require: '@open-xchange/appsuite-codeceptjs/src/plugins/testmetrics',
|
|
155
|
+
url: 'https://e2e-metrics.dev.oxui.de',
|
|
156
|
+
org: 'e2e',
|
|
157
|
+
defaultTags: {
|
|
158
|
+
client: process.env.E2E_TEST_METRICS_CLIENT || (process.env.CI && 'CI'),
|
|
159
|
+
pipelineId: process.env.CI_PIPELINE_ID,
|
|
160
|
+
branch: process.env.CI_COMMIT_REF_NAME,
|
|
161
|
+
project: process.env.CI_PROJECT_PATH
|
|
162
|
+
},
|
|
163
|
+
token: process.env.E2E_TEST_METRICS_TOKEN,
|
|
164
|
+
enabled: true
|
|
165
|
+
},
|
|
166
|
+
settingsInit: {
|
|
167
|
+
require: '@open-xchange/appsuite-codeceptjs/src/plugins/settingsInit',
|
|
168
|
+
enabled: true
|
|
169
|
+
},
|
|
170
|
+
customizePlugin: {
|
|
171
|
+
require: process.env.CUSTOMIZE_PLUGIN || '@open-xchange/appsuite-codeceptjs/src/plugins/emptyModule',
|
|
172
|
+
enabled: true
|
|
173
|
+
},
|
|
174
|
+
filterSuite: {
|
|
175
|
+
enabled: !process.env.E2E_GREP && process.env.CI && (process.env.SUITE_SERVICE_URL || process.env.FILTER_SUITE),
|
|
176
|
+
require: '@open-xchange/codecept-horizontal-scaler',
|
|
177
|
+
suiteFilePath: process.env.FILTER_SUITE,
|
|
178
|
+
filterFn: process.env.runOnly === 'true' ? () => false : undefined,
|
|
179
|
+
nodePrefix: 'core-ui'
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
rerun: {
|
|
183
|
+
minSuccess: Number(process.env.MIN_SUCCESS),
|
|
184
|
+
maxReruns: Number(process.env.MAX_RERUNS)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@open-xchange/appsuite-codeceptjs",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OX App Suite CodeceptJS Configuration and Helpers",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"e2e-rerun": "./customRerun.js"
|
|
8
|
+
},
|
|
9
|
+
"repository": "https://gitlab.open-xchange.com/frontend/codecept-helpers.git",
|
|
10
|
+
"license": "AGPL-3.0-or-later",
|
|
11
|
+
"private": false,
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@axe-core/playwright": "^4.8.5",
|
|
14
|
+
"@codeceptjs/helper": "^2.0.3",
|
|
15
|
+
"@influxdata/influxdb-client": "^1.33.2",
|
|
16
|
+
"@open-xchange/codecept-horizontal-scaler": "^0.1.7",
|
|
17
|
+
"@playwright/test": "^1.40.1",
|
|
18
|
+
"@types/node": "^20.11.19",
|
|
19
|
+
"allure-codeceptjs": "^2.12.2",
|
|
20
|
+
"chai": "^5.1.0",
|
|
21
|
+
"chai-subset": "^1.6.0",
|
|
22
|
+
"chalk": "^4.1.0",
|
|
23
|
+
"chalk-table": "^1.0.2",
|
|
24
|
+
"codeceptjs": "^3.5.14",
|
|
25
|
+
"dotenv": "^16.4.4",
|
|
26
|
+
"mocha": "^10.3.0",
|
|
27
|
+
"mocha-junit-reporter": "^2.2.1",
|
|
28
|
+
"mocha-multi": "^1.1.7",
|
|
29
|
+
"moment": "^2.30.1",
|
|
30
|
+
"moment-timezone": "^0.5.43",
|
|
31
|
+
"p-retry": "^6.2.0",
|
|
32
|
+
"playwright-core": "^1.40.1",
|
|
33
|
+
"playwright": "^1.40.1",
|
|
34
|
+
"short-uuid": "^4.2.2",
|
|
35
|
+
"soap": "^1.0.0",
|
|
36
|
+
"ts-node": "^10.9.2",
|
|
37
|
+
"typescript": "^5.3.3"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/actor.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com>
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
*
|
|
5
|
+
* This code is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
|
|
17
|
+
*
|
|
18
|
+
* Any use of the work other than as authorized under this license or copyright law is prohibited.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const { platform } = require('node:process')
|
|
22
|
+
const { expect } = require('@playwright/test')
|
|
23
|
+
const util = require('./util')
|
|
24
|
+
const codecept = require('codeceptjs')
|
|
25
|
+
|
|
26
|
+
const baseURL = util.getURLRoot()
|
|
27
|
+
|
|
28
|
+
module.exports = function () {
|
|
29
|
+
return actor({
|
|
30
|
+
|
|
31
|
+
amOnLoginPage () {
|
|
32
|
+
this.amOnPage(baseURL)
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
openApp (appName) {
|
|
36
|
+
this.click('~Navigate to:')
|
|
37
|
+
this.click(appName, '.apps')
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* This simplifies the input of mail addresses into input fields.
|
|
42
|
+
* There must be an array of users defined in the AppSuite helper configuration.
|
|
43
|
+
* If you want to fill in a mailaddress of such a user you can simply use this function.
|
|
44
|
+
*
|
|
45
|
+
* @param selector {string} the selector of an editable field
|
|
46
|
+
* @param userIndex {number} the users position in the users array provided via helper config
|
|
47
|
+
*/
|
|
48
|
+
insertMailaddress (selector, userIndex) {
|
|
49
|
+
const config = codecept.config.get()
|
|
50
|
+
const users = config.helpers.AppSuite.users
|
|
51
|
+
if (userIndex < 0 || userIndex >= users.length) userIndex = 0
|
|
52
|
+
const user = users[userIndex]
|
|
53
|
+
this.fillField(selector, user.mail)
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Logs in the user with the specified URL parameters and options.
|
|
58
|
+
* If the URL parameters are an object, it will be converted to an empty array.
|
|
59
|
+
* The options parameter is an object that can contain a 'user' property.
|
|
60
|
+
* If the 'user' property is not provided, the first user from the 'users' container will be used.
|
|
61
|
+
* The login process includes making an API request to the login endpoint with the user's credentials.
|
|
62
|
+
* After successful login, the page will be navigated to the specified URL with the URL parameters.
|
|
63
|
+
* If the 'isDeepLink' option is true, the URL will be constructed with a '#' prefix.
|
|
64
|
+
* The login process also includes waiting for the page to load and checking for the presence of the app.
|
|
65
|
+
* @param {string[]|object} urlParams - The URL parameters as an array or object.
|
|
66
|
+
* @param {object} options - The login options.
|
|
67
|
+
* @returns {Promise<void>} - A promise that resolves when the login process is complete.
|
|
68
|
+
*/
|
|
69
|
+
async login (urlParams, options) {
|
|
70
|
+
if (urlParams && !urlParams.length) {
|
|
71
|
+
// looks like an object, not string or array
|
|
72
|
+
options = urlParams
|
|
73
|
+
urlParams = []
|
|
74
|
+
}
|
|
75
|
+
urlParams = [].concat(urlParams || [])
|
|
76
|
+
options = Object.assign({ wait: true }, options)
|
|
77
|
+
let user = options.user || require('codeceptjs').container.support('users')[0]
|
|
78
|
+
|
|
79
|
+
if (typeof user === 'undefined') user = {}
|
|
80
|
+
if (user.toJSON) user = user.toJSON()
|
|
81
|
+
const loginName = process.env.PROVISIONING_API === 'reseller' ? user.name : `${user.name}${user.context.id ? '@' + user.context.id : ''}`
|
|
82
|
+
await this.makeApiRequest('POST', `${baseURL}/api/login`, {
|
|
83
|
+
params: { action: 'login', client: 'open-xchange-appsuite' },
|
|
84
|
+
form: {
|
|
85
|
+
name: loginName,
|
|
86
|
+
password: user.password
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
this.wait(0.5)
|
|
90
|
+
this.amOnPage(`${baseURL}/` + (options.isDeepLink ? '#' : '#!!') + (urlParams.length === 0 ? '' : '&' + urlParams.join('&')))
|
|
91
|
+
this.waitForInvisible('#background-loader.busy', util.getLoginTimeout())
|
|
92
|
+
if (options.wait) await this.waitForApp()
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
async logout () {
|
|
96
|
+
await this.makeApiRequest('POST', `${baseURL}/api/login`, { params: { action: 'logout' } })
|
|
97
|
+
this.amOnPage('about:blank')
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
waitForNetworkTraffic () {
|
|
101
|
+
this.waitForDetached({ css: '[aria-label="Currently refreshing"]' }, 15)
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
// Wait for focus in playwright
|
|
105
|
+
/**
|
|
106
|
+
* Waits for the specified element to receive focus.
|
|
107
|
+
*
|
|
108
|
+
* @param {string} locator - The locator of the element. Only accepts css selectors.
|
|
109
|
+
*/
|
|
110
|
+
waitForFocus (locator) {
|
|
111
|
+
this.waitForElement(locator)
|
|
112
|
+
this.waitForVisible(locator)
|
|
113
|
+
return this.usePlaywrightTo('wait for focus', async ({ page }) => {
|
|
114
|
+
await expect(page.locator(locator)).toBeFocused()
|
|
115
|
+
this.wait(0.5)
|
|
116
|
+
})
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
triggerRefresh () {
|
|
120
|
+
this.waitForVisible('~Refresh')
|
|
121
|
+
this.click('~Refresh')
|
|
122
|
+
this.waitForInvisible('~Currently refreshing', 10)
|
|
123
|
+
this.waitForVisible('~Refresh')
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
grabBackgroundImageFrom (selector) {
|
|
127
|
+
return this.grabCssPropertyFrom(selector, 'background-image').then(backgroundImage => {
|
|
128
|
+
backgroundImage = Array.isArray(backgroundImage) ? backgroundImage[0] : backgroundImage
|
|
129
|
+
return backgroundImage || 'none'
|
|
130
|
+
})
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
clickDropdown (text) {
|
|
134
|
+
this.waitForVisible('.dropdown.open .dropdown-menu, .primary-action > .open .dropdown-menu')
|
|
135
|
+
this.waitForText(text, 10, '.dropdown.open .dropdown-menu, .primary-action > .open .dropdown-menu')
|
|
136
|
+
return this.click(text, '.dropdown.open .dropdown-menu, .primary-action > .open .dropdown-menu')
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
clickToolbar (selector, timeout = 5) {
|
|
140
|
+
this.waitForElement(`.classic-toolbar [aria-label^="${selector}"]`, timeout)
|
|
141
|
+
this.click(`~${selector}`, '.classic-toolbar')
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
clickPrimary (text) {
|
|
145
|
+
const button = locate('.primary-action .btn-primary').withText(text).as(`Primary Button: ${text}`)
|
|
146
|
+
this.waitForVisible(button)
|
|
147
|
+
this.click(button)
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
openFolderMenu (folderName) {
|
|
151
|
+
const item = `.folder-tree [title*="${folderName}"]`
|
|
152
|
+
this.waitForVisible(item)
|
|
153
|
+
this.rightClick(item)
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
changeTheme ({ theme }) {
|
|
157
|
+
// "White" "Blue" "Steel gray" "Dark" "Mountains" "Beach" "City" "Blue Sunset"
|
|
158
|
+
this.click('[aria-label="Settings"]')
|
|
159
|
+
this.waitForVisible('#topbar-settings-dropdown')
|
|
160
|
+
this.click(`div[aria-label="Themes"] a[title="${theme}"]`)
|
|
161
|
+
this.waitForNetworkTraffic()
|
|
162
|
+
this.pressKey('Escape')
|
|
163
|
+
this.wait(0.5)
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Selects the text in a text field or contenteditable element from cursor position to beginning of line.
|
|
168
|
+
*/
|
|
169
|
+
selectLine () {
|
|
170
|
+
const shortcut = platform === 'darwin' ? ['Shift', 'CommandOrControl', 'ArrowLeft'] : ['Shift', 'Home']
|
|
171
|
+
this.pressKey(shortcut)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
}
|