@open-xchange/appsuite-codeceptjs 0.6.17 → 0.6.19
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/index.js +1 -0
- package/package.json +3 -3
- package/src/actor.js +22 -19
- package/src/appsuiteHttpClient.js +2 -0
- package/src/helper.js +0 -7
- package/src/sharedaccounts/sharedaccounts.js +180 -0
- package/src/sharedaccounts.js +23 -0
- package/steps.d.ts +2 -3
package/index.js
CHANGED
|
@@ -89,6 +89,7 @@ module.exports = {
|
|
|
89
89
|
I: '@open-xchange/appsuite-codeceptjs/src/actor',
|
|
90
90
|
users: '@open-xchange/appsuite-codeceptjs/src/users',
|
|
91
91
|
contexts: '@open-xchange/appsuite-codeceptjs/src/contexts',
|
|
92
|
+
sharedaccounts: '@open-xchange/appsuite-codeceptjs/src/sharedaccounts',
|
|
92
93
|
...pageobjects
|
|
93
94
|
},
|
|
94
95
|
async bootstrap () {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-xchange/appsuite-codeceptjs",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.19",
|
|
4
4
|
"description": "OX App Suite CodeceptJS Configuration and Helpers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"p-retry": "^7.1.1",
|
|
34
34
|
"playwright-core": "1.58.2",
|
|
35
35
|
"short-uuid": "^6.0.3",
|
|
36
|
-
"@open-xchange/
|
|
37
|
-
"@open-xchange/
|
|
36
|
+
"@open-xchange/soap-client": "0.0.11",
|
|
37
|
+
"@open-xchange/codecept-horizontal-scaler": "0.1.14"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^24.10.3",
|
package/src/actor.js
CHANGED
|
@@ -32,9 +32,16 @@ module.exports = function (customSteps) {
|
|
|
32
32
|
this.amOnPage(baseURL)
|
|
33
33
|
},
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
waitForApp () {
|
|
36
|
+
this.waitForInvisible('#background-loader.busy', 30)
|
|
37
|
+
this.waitForVisible({ css: 'html.complete' }, 10)
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
openApp (appName, options) {
|
|
36
41
|
this.click('~Navigate to:')
|
|
37
42
|
this.click(appName, '.apps')
|
|
43
|
+
options = { wait: true, ...options }
|
|
44
|
+
if (options.wait) this.waitForApp()
|
|
38
45
|
},
|
|
39
46
|
|
|
40
47
|
/**
|
|
@@ -62,36 +69,33 @@ module.exports = function (customSteps) {
|
|
|
62
69
|
* After successful login, the page will be navigated to the specified URL with the URL parameters.
|
|
63
70
|
* If the 'isDeepLink' option is true, the URL will be constructed with a '#' prefix.
|
|
64
71
|
* The login process also includes waiting for the page to load and checking for the presence of the app.
|
|
65
|
-
* @param {string[]|
|
|
72
|
+
* @param {string[]|string} urlParams - The URL parameters as an array or string.
|
|
66
73
|
* @param {object} options - The login options.
|
|
67
|
-
* @returns {Promise<void>} - A promise that resolves when the login process is complete.
|
|
68
74
|
*/
|
|
69
|
-
|
|
75
|
+
login (urlParams, options) {
|
|
70
76
|
if (urlParams && !urlParams.length) {
|
|
71
77
|
// looks like an object, not string or array
|
|
72
78
|
options = urlParams
|
|
73
|
-
urlParams =
|
|
79
|
+
urlParams = undefined
|
|
74
80
|
}
|
|
81
|
+
options = { wait: true, ...options }
|
|
75
82
|
urlParams = [].concat(urlParams || [])
|
|
76
|
-
options
|
|
83
|
+
if (!options.isDeepLink) urlParams.unshift('!!')
|
|
77
84
|
let user = util.resolveUser(options.user)
|
|
78
85
|
if (user.toJSON) user = user.toJSON()
|
|
79
86
|
const loginName = process.env.PROVISIONING_API === 'reseller' ? user.name : `${user.name}${user.context.id ? '@' + user.context.id : ''}`
|
|
80
|
-
|
|
87
|
+
this.makeApiRequest('POST', `${baseURL}/api/login`, {
|
|
81
88
|
params: { action: 'login', client: 'open-xchange-appsuite' },
|
|
82
|
-
form: {
|
|
83
|
-
name: loginName,
|
|
84
|
-
password: user.password
|
|
85
|
-
}
|
|
89
|
+
form: { name: loginName, password: user.password }
|
|
86
90
|
})
|
|
87
91
|
this.wait(0.5)
|
|
88
|
-
this.amOnPage(`${baseURL}
|
|
92
|
+
this.amOnPage(`${baseURL}/#${urlParams.join('&')}`)
|
|
89
93
|
this.waitForInvisible('#background-loader.busy', util.getLoginTimeout())
|
|
90
|
-
if (options.wait)
|
|
94
|
+
if (options.wait) this.waitForVisible({ css: 'html.complete' }, 10)
|
|
91
95
|
},
|
|
92
96
|
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
logout () {
|
|
98
|
+
this.makeApiRequest('POST', `${baseURL}/api/login`, { params: { action: 'logout' } })
|
|
95
99
|
this.amOnPage('about:blank')
|
|
96
100
|
},
|
|
97
101
|
|
|
@@ -105,10 +109,9 @@ module.exports = function (customSteps) {
|
|
|
105
109
|
*
|
|
106
110
|
* @param {string} locator - The locator of the element. Only accepts css selectors.
|
|
107
111
|
*/
|
|
108
|
-
|
|
109
|
-
this.waitForElement(locator)
|
|
112
|
+
waitForFocus (locator) {
|
|
110
113
|
this.waitForVisible(locator)
|
|
111
|
-
|
|
114
|
+
this.usePlaywrightTo('wait for focus', async ({ page }) => {
|
|
112
115
|
await expect(page.locator(locator)).toBeFocused()
|
|
113
116
|
this.wait(0.5)
|
|
114
117
|
})
|
|
@@ -131,7 +134,7 @@ module.exports = function (customSteps) {
|
|
|
131
134
|
clickDropdown (text) {
|
|
132
135
|
this.waitForVisible('.dropdown.open .dropdown-menu, .primary-action > .open .dropdown-menu')
|
|
133
136
|
this.waitForText(text, 10, '.dropdown.open .dropdown-menu, .primary-action > .open .dropdown-menu')
|
|
134
|
-
|
|
137
|
+
this.click(text, '.dropdown.open .dropdown-menu, .primary-action > .open .dropdown-menu')
|
|
135
138
|
},
|
|
136
139
|
|
|
137
140
|
clickToolbar (selector, timeout = 5) {
|
|
@@ -61,8 +61,10 @@ async function fetchWithRetry (url, options) {
|
|
|
61
61
|
function createHttpClient (options) {
|
|
62
62
|
let user = util.resolveUser(options?.user)
|
|
63
63
|
if (user.toJSON) user = user.toJSON()
|
|
64
|
+
const params = options?.params
|
|
64
65
|
|
|
65
66
|
async function request (url, options = {}) {
|
|
67
|
+
if (params) options.params = { ...params, ...options.params }
|
|
66
68
|
if (user) {
|
|
67
69
|
if (!cache[user.name]) await login(user)
|
|
68
70
|
options.headers = { Cookie: cache[user.name].cookies, ...options.headers }
|
package/src/helper.js
CHANGED
|
@@ -919,13 +919,6 @@ class AppSuiteHelper extends Helper {
|
|
|
919
919
|
)
|
|
920
920
|
}
|
|
921
921
|
|
|
922
|
-
async waitForApp () {
|
|
923
|
-
const { Playwright } = this.helpers
|
|
924
|
-
|
|
925
|
-
await Playwright.waitForInvisible('#background-loader.busy', 30)
|
|
926
|
-
await Playwright.waitForVisible({ css: 'html.complete' }, 10)
|
|
927
|
-
}
|
|
928
|
-
|
|
929
922
|
async copyToClipboard () {
|
|
930
923
|
const helper = this.helpers.Playwright
|
|
931
924
|
const { page } = helper
|
|
@@ -0,0 +1,180 @@
|
|
|
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 short = require('short-uuid')
|
|
22
|
+
const { sharedAccountService } = require('@open-xchange/soap-client/common')
|
|
23
|
+
const { util } = require('../util')
|
|
24
|
+
|
|
25
|
+
const sharedAccounts = []
|
|
26
|
+
|
|
27
|
+
class SharedAccount {
|
|
28
|
+
constructor (opt) {
|
|
29
|
+
this.sharedAccountData = opt.sharedAccountData
|
|
30
|
+
this.context = opt.context
|
|
31
|
+
return new Proxy(this, {
|
|
32
|
+
get (target, prop) {
|
|
33
|
+
return prop in target ? target[prop] : target.sharedAccountData[prop]
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static getRandom () {
|
|
39
|
+
const id = `e2e-${short.generate().slice(0, 9).toLowerCase()}`
|
|
40
|
+
const domain = util.mxDomain()
|
|
41
|
+
const name = `${id}-Anmeldung`
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
imapServer: util.imapServer(),
|
|
45
|
+
smtpServer: util.smtpServer(),
|
|
46
|
+
language: 'de_DE',
|
|
47
|
+
mailenabled: 1,
|
|
48
|
+
name,
|
|
49
|
+
display_name: name,
|
|
50
|
+
imapLogin: name,
|
|
51
|
+
primaryEmail: `${id}-anmeldung@${domain}`,
|
|
52
|
+
email1: `${id}-anmeldung@${domain}`,
|
|
53
|
+
timezone: 'Europe/Berlin',
|
|
54
|
+
password: 'secret'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create a shared account with the specified account data:
|
|
60
|
+
*
|
|
61
|
+
* @param {object} sharedAccountData
|
|
62
|
+
* Properties:
|
|
63
|
+
* - display_name
|
|
64
|
+
* - imapServer
|
|
65
|
+
* - smtpServer
|
|
66
|
+
* - language
|
|
67
|
+
* - mailenabled
|
|
68
|
+
* - name
|
|
69
|
+
* - primaryEmail
|
|
70
|
+
* - email1
|
|
71
|
+
* - timezone
|
|
72
|
+
* - password
|
|
73
|
+
*
|
|
74
|
+
* @param {*} ctx
|
|
75
|
+
*
|
|
76
|
+
* @returns {Promise<Object>} A promise resolving with the created shared account object.
|
|
77
|
+
*/
|
|
78
|
+
static async create (sharedAccountData, ctx = {}) {
|
|
79
|
+
sharedAccountData = { ...this.getRandom(), ...sharedAccountData }
|
|
80
|
+
|
|
81
|
+
const { contexts } = inject()
|
|
82
|
+
let sharedAccountContext
|
|
83
|
+
try {
|
|
84
|
+
// @ts-ignore
|
|
85
|
+
sharedAccountContext = await contexts.reuse(ctx, ctx.admin, ctx.auth)
|
|
86
|
+
} catch (e) {
|
|
87
|
+
// @ts-ignore
|
|
88
|
+
sharedAccountContext = await contexts.create(Object.assign({ maxQuota: 1000 }, ctx))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const data = await sharedAccountService.create(sharedAccountContext, sharedAccountData)
|
|
92
|
+
const sharedAccount = new SharedAccount({ sharedAccountData: data, context: sharedAccountContext })
|
|
93
|
+
sharedAccounts.push(sharedAccount)
|
|
94
|
+
return sharedAccount
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get the default configuration for creating shared account permissions for mail and calendar.
|
|
99
|
+
*
|
|
100
|
+
* @returns {Object} The default configuration object.
|
|
101
|
+
*/
|
|
102
|
+
static getDefaultConfig () {
|
|
103
|
+
return {
|
|
104
|
+
permissionLevel: 'author',
|
|
105
|
+
// grantedCapability: ['sendAs', 'sendOnBehalf', 'snippets', 'writeSnippets'],
|
|
106
|
+
grantedCapability: [],
|
|
107
|
+
deniedCapability: []
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Add users and groups with specified permissions and capabilities to the shared account using the OX Shared Account Service.
|
|
113
|
+
*
|
|
114
|
+
* @returns {Promise<void>}
|
|
115
|
+
*/
|
|
116
|
+
async createPermissions ({ users = [], groups = [], mail = SharedAccount.getDefaultConfig(), calendar = SharedAccount.getDefaultConfig() }) {
|
|
117
|
+
// use context of the first user or the first group
|
|
118
|
+
// -> in every call to 'createSharedAccountPermissions' all users and groups must be in the same context
|
|
119
|
+
const context = users[0].context || groups[0].context
|
|
120
|
+
|
|
121
|
+
return sharedAccountService.createSharedAccountPermissions(context, this.context, this.sharedAccountData, mail, calendar, users, groups)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* List all shared accounts in the specified context.
|
|
126
|
+
*
|
|
127
|
+
* @returns {Promise<Object[]>} A promise resolving with the list of shared account objects.
|
|
128
|
+
*/
|
|
129
|
+
static async list (context) {
|
|
130
|
+
return sharedAccountService.list(context)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Returns the fully qualified identifier of the shared account.
|
|
135
|
+
*
|
|
136
|
+
* @returns {string}
|
|
137
|
+
* The fully qualified identifier of the shared account.
|
|
138
|
+
*/
|
|
139
|
+
getFullId () {
|
|
140
|
+
return this.sharedAccountData.id + '@' + this.context.id
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get all data of the shared account.
|
|
145
|
+
*
|
|
146
|
+
* @returns {Promise<Object>} A promise resolving with all data of the shared account.
|
|
147
|
+
*/
|
|
148
|
+
async getData () {
|
|
149
|
+
return sharedAccountService.getData(this.context, this.sharedAccountData)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Remove the shared account.
|
|
154
|
+
*
|
|
155
|
+
* @returns {Promise<void>}
|
|
156
|
+
*/
|
|
157
|
+
async remove () {
|
|
158
|
+
const index = sharedAccounts.indexOf(this)
|
|
159
|
+
if (index < 0) return
|
|
160
|
+
sharedAccounts.splice(index, 1)
|
|
161
|
+
return sharedAccountService.remove(this.context, this.sharedAccountData)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
toJSON () {
|
|
165
|
+
return {
|
|
166
|
+
context: { ...this.context },
|
|
167
|
+
...this.sharedAccountData
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = () => {
|
|
173
|
+
return new Proxy(sharedAccounts, {
|
|
174
|
+
// act like an array if an index is being used
|
|
175
|
+
get (target, prop) {
|
|
176
|
+
if (prop in target) { return target[prop] }
|
|
177
|
+
return SharedAccount[prop]
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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 moduleToLoad = './sharedaccounts/sharedaccounts'
|
|
22
|
+
|
|
23
|
+
module.exports = require(moduleToLoad)
|
package/steps.d.ts
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
type steps_file = typeof import('@codeceptjs/configure/test/integration/steps_file.js')
|
|
3
3
|
type users = typeof import('./src/users.js')
|
|
4
4
|
type contexts = typeof import('./src/contexts.js')
|
|
5
|
-
type
|
|
6
|
-
type ResellerUser = typeof import('./src/reseller/user.js')
|
|
5
|
+
type sharedaccounts = typeof import('./src/sharedaccounts.js')
|
|
7
6
|
type AppSuite = import('./src/helper.js')
|
|
8
7
|
|
|
9
8
|
declare namespace CodeceptJS {
|
|
10
|
-
interface SupportObject { I: I, current: any, users: users, contexts: contexts,
|
|
9
|
+
interface SupportObject { I: I, current: any, users: users, contexts: contexts, sharedaccounts: sharedaccounts }
|
|
11
10
|
interface Methods extends Playwright, AppSuite {}
|
|
12
11
|
interface I extends ReturnType<steps_file>, WithTranslation<AppSuite> {}
|
|
13
12
|
namespace Translation {
|