@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.
Files changed (48) hide show
  1. package/.env.defaults +47 -0
  2. package/README.md +40 -0
  3. package/chai.d.ts +5 -0
  4. package/customRerun.js +135 -0
  5. package/global.d.ts +5 -0
  6. package/index.js +187 -0
  7. package/package.json +39 -0
  8. package/src/actor.js +174 -0
  9. package/src/appsuiteHttpClient.js +155 -0
  10. package/src/chai.d.ts +6 -0
  11. package/src/chai.js +58 -0
  12. package/src/contexts/contexts.js +172 -0
  13. package/src/contexts/reseller.js +248 -0
  14. package/src/contexts.js +29 -0
  15. package/src/event.js +54 -0
  16. package/src/helper.js +817 -0
  17. package/src/pageobjects/calendar.js +226 -0
  18. package/src/pageobjects/contacts.js +148 -0
  19. package/src/pageobjects/drive.js +96 -0
  20. package/src/pageobjects/fragments/contact-autocomplete.js +45 -0
  21. package/src/pageobjects/fragments/contact-picker.js +50 -0
  22. package/src/pageobjects/fragments/dialogs.js +41 -0
  23. package/src/pageobjects/fragments/search.js +54 -0
  24. package/src/pageobjects/fragments/settings-mailfilter.js +90 -0
  25. package/src/pageobjects/fragments/settings.js +71 -0
  26. package/src/pageobjects/fragments/tinymce.js +41 -0
  27. package/src/pageobjects/fragments/topbar.js +43 -0
  28. package/src/pageobjects/fragments/viewer.js +67 -0
  29. package/src/pageobjects/mail.js +67 -0
  30. package/src/pageobjects/mobile/mobileCalendar.js +41 -0
  31. package/src/pageobjects/mobile/mobileContacts.js +40 -0
  32. package/src/pageobjects/mobile/mobileMail.js +51 -0
  33. package/src/pageobjects/tasks.js +58 -0
  34. package/src/plugins/emptyModule/index.js +21 -0
  35. package/src/plugins/settingsInit/index.js +35 -0
  36. package/src/plugins/testmetrics/index.js +135 -0
  37. package/src/soap/services/context.js +147 -0
  38. package/src/soap/services/oxaas.js +36 -0
  39. package/src/soap/services/resellerContext.js +65 -0
  40. package/src/soap/services/resellerUser.js +100 -0
  41. package/src/soap/services/user.js +114 -0
  42. package/src/soap/services/util.js +39 -0
  43. package/src/soap/soap.js +172 -0
  44. package/src/users/reseller.js +233 -0
  45. package/src/users/users.js +183 -0
  46. package/src/users.js +29 -0
  47. package/src/util.js +104 -0
  48. 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
@@ -0,0 +1,5 @@
1
+ declare module Chai {
2
+ interface Assertion {
3
+ accessible: void;
4
+ }
5
+ }
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
@@ -0,0 +1,5 @@
1
+ // IMPORTANT: This solution only works by declaring variables with var (do not use let or const)
2
+ var expect: Chai.ExpectStatic;
3
+ var assert: Chai.AssertStatic;
4
+ const codecept_dir: string;
5
+ const output_dir: string;
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
+ }