@open-xchange/appsuite-codeceptjs 0.7.1 → 0.8.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/CHANGELOG.md ADDED
@@ -0,0 +1,167 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.8.0] - 2026-03-15
8
+
9
+ ### Changed
10
+
11
+ - Replace chalk and dotenv with Node.js built-ins
12
+ - Clean up dependencies, packaging, and config files
13
+ - Clean up Chromium args and add renderer process limit
14
+
15
+ ## [0.7.2] - 2026-03-09
16
+
17
+ ### Added
18
+
19
+ - TypeScript type definitions
20
+
21
+ ## [0.7.0] - 2026-02-24
22
+
23
+ ### Changed
24
+
25
+ - Switch container base image to wolfi/playwright and make output directory configurable
26
+
27
+ ## [0.6.20] - 2026-02-24
28
+
29
+ ### Fixed
30
+
31
+ - Fix typo in import statement
32
+
33
+ ## [0.6.19] - 2026-02-24
34
+
35
+ ### Added
36
+
37
+ - Support for shared accounts in e2e tests
38
+
39
+ ## [0.6.18] - 2026-02-23
40
+
41
+ ### Changed
42
+
43
+ - Convert various step functions to sync
44
+
45
+ ## [0.6.17] - 2026-02-13
46
+
47
+ ### Added
48
+
49
+ - Helpers for manipulating user settings in tests
50
+ - Allow passing more custom steps to actor()
51
+
52
+ ### Fixed
53
+
54
+ - Internal Node exception when requiring appsuite-codeceptjs and chai (#18)
55
+
56
+ ## [0.6.16] - 2026-01-22
57
+
58
+ ### Added
59
+
60
+ - `e2e-context-` prefix check to prevent accidental context deletion
61
+ - Export "actor" from main module
62
+
63
+ ### Changed
64
+
65
+ - Use existing helper instead of creating new instance for every user
66
+
67
+ ### Fixed
68
+
69
+ - Location of "reporter" option inside "mocha" object
70
+ - Move `util.getURLRoot` call into actor
71
+
72
+ ### Removed
73
+
74
+ - Unnecessary default context fetch on module load
75
+
76
+ ## [0.6.14] - 2025-12-12
77
+
78
+ ### Changed
79
+
80
+ - Switch Playwright base image from jammy to noble
81
+
82
+ ## [0.6.13] - 2025-09-30
83
+
84
+ ### Fixed
85
+
86
+ - Unhandled rejection timeout errors in soap-client
87
+
88
+ ### Removed
89
+
90
+ - Unit tests (moved elsewhere)
91
+
92
+ ## [0.6.12] - 2025-09-25
93
+
94
+ ### Changed
95
+
96
+ - Use pageobjects package from core-ui
97
+
98
+ ## [0.6.10] - 2025-09-15
99
+
100
+ ### Fixed
101
+
102
+ - Use job group name as node-prefix for filter suite
103
+
104
+ ## [0.6.7] - 2025-07-18
105
+
106
+ ### Fixed
107
+
108
+ - Do not load common contexts when using reseller API
109
+
110
+ ## [0.6.4] - 2025-06-13
111
+
112
+ ### Changed
113
+
114
+ - Update pageobjects and actor to work with unpatched CodeceptJS
115
+
116
+ ## [0.6.3] - 2025-06-11
117
+
118
+ ### Added
119
+
120
+ - Documentation for overwriting helpers locally
121
+
122
+ ### Changed
123
+
124
+ - Move soap to own library
125
+ - Clean up codeceptjs dependencies
126
+ - Update paths for gitlab.com move
127
+
128
+ ### Fixed
129
+
130
+ - Setting startDate and endDate in calendar/createAppointment
131
+ - getNextMonday (pageobjects/calendar) to work on Sunday correctly
132
+
133
+ ### Removed
134
+
135
+ - chai-subset (now merged with chai)
136
+
137
+ ## [0.6.2] - 2024-12-17
138
+
139
+ ### Changed
140
+
141
+ - Replace Jest with Vitest and improve test infrastructure
142
+ - Switch to neostandard library
143
+
144
+ ## [0.6.0] - 2024-10-10
145
+
146
+ ### Added
147
+
148
+ - allure-js-commons module
149
+
150
+ ## [0.5.0] - 2024-09-30
151
+
152
+ ### Added
153
+
154
+ - E2E setup and testing for appsuite-codeceptjs
155
+ - Container image
156
+
157
+ ## [0.4.4] - 2024-08-27
158
+
159
+ ### Fixed
160
+
161
+ - Reseller API support for pre-assembled contexts
162
+
163
+ ## [0.4.3] - 2024-08-22
164
+
165
+ ### Added
166
+
167
+ - Initial release
package/README.md CHANGED
@@ -55,18 +55,4 @@ const { config } = require('@open-xchange/appsuite-codeceptjs')
55
55
  config.tests = './costum_directory/*_test.js'
56
56
 
57
57
  module.exports.config = config
58
- ```
59
-
60
- ## Known Issues
61
-
62
- Add this to your `package.json` to ignore CVE-2025-5889 when installing this package with `pnpm`:
63
-
64
- ```
65
- "pnpm": {
66
- "auditConfig": {
67
- "ignoreCves": [
68
- "CVE-2025-5889"
69
- ]
70
- }
71
- }
72
- ```
58
+ ```
package/index.d.ts ADDED
@@ -0,0 +1,598 @@
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
+ declare module '@open-xchange/appsuite-codeceptjs' {
22
+
23
+ import Helper from '@codeceptjs/helper'
24
+ import { Page, Browser, BrowserContext, Request, Response } from 'playwright-core'
25
+ import * as OXPO from '@open-xchange/appsuite-codeceptjs-pageobjects'
26
+
27
+ export { recorder, event as codeceptEvents } from 'codeceptjs'
28
+
29
+ // util ---------------------------------------------------------------------
30
+
31
+ /**
32
+ * Converts the return type `Promise<void>` of all methods in `T` to `void`.
33
+ *
34
+ * @template T
35
+ * The interface with methods to be converted.
36
+ */
37
+ export type SyncifyVoidMethods<T> = {
38
+ [K in keyof T]: T[K] extends (...args: infer A) => Promise<infer R> ? R extends void ? (...args: A) => void : T[K] : T[K]
39
+ }
40
+
41
+ class PropagatedError extends Error {
42
+ constructor (error: Error)
43
+ }
44
+
45
+ export type { PropagatedError }
46
+
47
+ export const util: {
48
+ getLoginTimeout (): number
49
+ getURLRoot (): string
50
+ getServerURL (): string
51
+ getJavascriptRoot (): string
52
+ userContextId (): number
53
+ admin (): string
54
+ mxDomain (): string
55
+ smtpServer (): string
56
+ imapServer (): string
57
+ getDefaultUserPassword (): string
58
+ PropagatedError: typeof PropagatedError
59
+ addJitter (id: number): number
60
+ }
61
+
62
+ // config -------------------------------------------------------------------
63
+
64
+ export interface AppSuiteCodeceptConfig extends CodeceptJS.MainConfig {
65
+ helpers: NonNullable<CodeceptJS.MainConfig['helpers']>
66
+ include: NonNullable<CodeceptJS.MainConfig['include']>
67
+ plugins: NonNullable<CodeceptJS.MainConfig['plugins']>
68
+ }
69
+
70
+ export const config: AppSuiteCodeceptConfig
71
+
72
+ // contexts -----------------------------------------------------------------
73
+
74
+ export interface UserAttributes {
75
+ entries: Array<{ key: string; value: unknown }>
76
+ }
77
+
78
+ export interface ContextData {
79
+ enabled: boolean
80
+ filestoreId: number
81
+ filestore_name: string
82
+ id: number
83
+ loginMappings: string[]
84
+ maxQuota: number
85
+ name: string
86
+ userAttributes: UserAttributes
87
+ }
88
+
89
+ export interface AuthData {
90
+ login: string
91
+ password: string
92
+ }
93
+
94
+ export interface ContextAdmin extends AuthData {
95
+ display_name: string
96
+ email1: string
97
+ given_name: string
98
+ name: string
99
+ primaryEmail: string
100
+ sur_name: string
101
+ }
102
+
103
+ export type ModuleAccess = Record<string, boolean>
104
+
105
+ class Context {
106
+ readonly id: number
107
+ readonly ctxdata: ContextData
108
+ readonly admin: ContextAdmin
109
+ readonly auth: AuthData
110
+ constructor (options: { ctxdata: ContextData; admin: ContextAdmin; auth: AuthData })
111
+ remove (): Promise<void>
112
+ hasConfig (key: string, value: unknown): Promise<void>
113
+ hasCapability (capability: string): Promise<void>
114
+ doesntHaveCapability (capability: string): Promise<void>
115
+ hasAccessCombination (accessCombinationName: string): Promise<void>
116
+ getModuleAccess (): Promise<ModuleAccess>
117
+ hasModuleAccess (moduleAccess: ModuleAccess): Promise<void>
118
+ hasQuota (maxQuota: number): Promise<void>
119
+ }
120
+
121
+ export type { Context }
122
+
123
+ export interface Contexts extends ReadonlyArray<Context> {
124
+ create (ctx?: { filestoreId?: number; id?: string; maxQuota?: number }): Promise<Context>
125
+ reuse (ctx: Context, admin?: ContextAdmin, auth?: AuthData): Promise<Context>
126
+ removeAll (auth?: AuthData): Promise<void>
127
+ }
128
+
129
+ // users --------------------------------------------------------------------
130
+
131
+ export interface UserData {
132
+ aliases: string[]
133
+ contextadmin: boolean
134
+ defaultSenderAddress: string
135
+ display_name: string
136
+ email1: string
137
+ given_name: string
138
+ id: number
139
+ imapLogin: string
140
+ imapPort: number
141
+ imapSchema: string
142
+ imapServer: string
143
+ imapServerString: string
144
+ language: string
145
+ mailenabled: boolean
146
+ name: string
147
+ password: string
148
+ passwordMech: string
149
+ password_expired: boolean
150
+ primaryEmail: string
151
+ smtpPort: number
152
+ smtpSchema: string
153
+ smtpServer: string
154
+ smtpServerString: string
155
+ sur_name: string
156
+ userAttributes: UserAttributes
157
+ convert_drive_user_folders: boolean
158
+ }
159
+
160
+ class User {
161
+ readonly userdata: UserData
162
+ readonly context: Context
163
+ constructor (options: { user: UserData, context: Context })
164
+ remove (): Promise<void>
165
+ hasConfig (key: string, value: unknown): Promise<void>
166
+ hasAlias (alias: string): Promise<void>
167
+ doesntHaveAlias (alias: string): Promise<void>
168
+ hasAccessCombination (accessCombinationName: string): Promise<void>
169
+ getModuleAccess (): Promise<ModuleAccess>
170
+ hasModuleAccess (moduleAccess: ModuleAccess): Promise<void>
171
+ hasCapability (capability: string): Promise<void>
172
+ doesntHaveCapability (capability: string): Promise<void>
173
+ login (): string
174
+ get <K extends keyof UserData> (param: K): UserData[K]
175
+ toJSON (): UserData & { context: ContextData & { admin: ContextAdmin; auth: AuthData } }
176
+ }
177
+
178
+ export type { User }
179
+
180
+ export interface Users extends ReadonlyArray<User> {
181
+ getRandom (user?: Partial<UserData>): Partial<UserData>
182
+ create (user?: Partial<UserData>, context?: Context): Promise<User>
183
+ change (userdata: Partial<UserData>): Promise<void>
184
+ removeAll (): Promise<void>
185
+ }
186
+
187
+ // shared accounts ----------------------------------------------------------
188
+
189
+ export interface SharedAccountData {
190
+ imapServer: string
191
+ smtpServer: string
192
+ language: string
193
+ mailenabled: number
194
+ name: string
195
+ display_name: string
196
+ imapLogin: string
197
+ primaryEmail: string
198
+ email1: string
199
+ timezone: string
200
+ password: string
201
+ }
202
+
203
+ export type SharedAccountCapability = 'sendAs' | 'sendOnBehalf' | 'snippets' | 'writeSnippets' | 'manageSieve'
204
+
205
+ export interface SharedAccountModuleConfig {
206
+ permissionLevel: 'viewer' | 'editor' | 'author' | 'admin'
207
+ grantedCapability: SharedAccountCapability[]
208
+ deniedCapability: SharedAccountCapability[]
209
+ }
210
+
211
+ class SharedAccount {
212
+ readonly sharedAccountData: SharedAccountData
213
+ readonly context: Context
214
+ constructor (options: { sharedAccountData: SharedAccountData, context: Context })
215
+ createPermissions (options?: { users?: User[], groups?: User[], mail?: SharedAccountModuleConfig, calendar?: SharedAccountModuleConfig }): Promise<void>
216
+ getFullId (): string
217
+ getData (): Promise<SharedAccountData>
218
+ remove (): Promise<void>
219
+ toJSON (): SharedAccountData & { context: ContextData & { admin: ContextAdmin; auth: AuthData } }
220
+ }
221
+
222
+ export type { SharedAccount }
223
+
224
+ export interface SharedAccounts extends ReadonlyArray<SharedAccount> {
225
+ getRandom (): SharedAccountData
226
+ create (sharedAccountData?: Partial<SharedAccountData>, ctx?: Context): Promise<SharedAccount>
227
+ getDefaultConfig (): { permissionLevel: string; grantedCapability: string[]; deniedCapability: string[] }
228
+ list (context: Context): Promise<SharedAccountData[]>
229
+ }
230
+
231
+ // event --------------------------------------------------------------------
232
+
233
+ export interface AppSuiteCodeceptEventMap {
234
+ 'provisioning.user.create': [user: UserData, context: Context]
235
+ 'provisioning.user.created': [user: User]
236
+ 'provisioning.user.removed': [user: User]
237
+ 'provisioning.context.create': [context: ContextData, admin: ContextAdmin, auth: AuthData]
238
+ 'provisioning.context.created': [context: Context]
239
+ 'provisioning.context.removed': [context: Context]
240
+ }
241
+
242
+ export const event: {
243
+ readonly provisioning: {
244
+ readonly user: {
245
+ readonly create: 'provisioning.user.create'
246
+ readonly created: 'provisioning.user.created'
247
+ readonly removed: 'provisioning.user.removed'
248
+ }
249
+ readonly context: {
250
+ readonly create: 'provisioning.context.create'
251
+ readonly created: 'provisioning.context.created'
252
+ readonly removed: 'provisioning.context.removed'
253
+ }
254
+ }
255
+ dispatcher: NodeJS.EventEmitter<AppSuiteCodeceptEventMap>
256
+ emit <K extends keyof AppSuiteCodeceptEventMap> (event: K, ...params: AppSuiteCodeceptEventMap[K]): void
257
+ }
258
+
259
+ // helper -------------------------------------------------------------------
260
+
261
+ export interface UserOptions {
262
+ /**
263
+ * The user account to be used. Default is the first created user.
264
+ * @default users[0]
265
+ */
266
+ user?: User
267
+ }
268
+
269
+ export type MailAddress = [name: string, address: string]
270
+
271
+ export interface MailAttachment {
272
+ id?: string
273
+ content_type: string
274
+ content?: string
275
+ filename?: string
276
+ size?: number
277
+ disp?: 'inline' | 'attachment' | 'alternative'
278
+ }
279
+
280
+ export interface MailSecurity {
281
+ encrypt?: boolean
282
+ sign?: boolean
283
+ type?: 'pgp' | 'smime'
284
+ }
285
+
286
+ export interface HaveMailData {
287
+ folder?: string
288
+ from: MailAddress[]
289
+ to: MailAddress[]
290
+ subject?: string
291
+ sendtype: number
292
+ attachments?: MailAttachment[]
293
+ security?: MailSecurity
294
+ }
295
+
296
+ export interface HaveMailFromFile {
297
+ folder?: string
298
+ path: string
299
+ }
300
+
301
+ export interface GrabDefaultFolderOptions extends UserOptions {
302
+ type?: string
303
+ }
304
+
305
+ export interface HaveFileData {
306
+ filename: string
307
+ contents: BlobPart
308
+ }
309
+
310
+ export interface HaveFileOptions extends UserOptions {
311
+ cryptoAction?: 'encrypt'
312
+ }
313
+
314
+ export interface AppSuiteHelper extends Helper {
315
+ waitForDownload (locator: string, filename: string, referenceFilePath?: string): Promise<void>
316
+ grabAxeReport (options?: { disableRules?: string | string[]; exclude?: string | string[]; include?: string | string[] }): Promise<object>
317
+ selectFolder (id: string, context?: string): Promise<void>
318
+ throttleNetwork (networkConfig: 'OFFLINE' | 'GPRS' | '2G' | '3G' | '4G' | 'DSL' | 'ONLINE'): Promise<void>
319
+ haveSetting (settings: Record<string, unknown>, options?: UserOptions): Promise<void>
320
+ haveSetting (key: string, value: unknown, options?: UserOptions): Promise<void>
321
+ /**
322
+ * Changes a configuration item of a user account during a test.
323
+ *
324
+ * Use this method inside test scenarios instead of calling `await
325
+ * user.hasConfig()` directly to make this call part of the recorder queue,
326
+ * instead of executing it immediately during test step registration.
327
+ *
328
+ * @param key
329
+ * The configuration key.
330
+ *
331
+ * @param value
332
+ * The new configuration value.
333
+ *
334
+ * @param options
335
+ * Optional parameters.
336
+ */
337
+ haveConfig (key: string, value: unknown, options?: UserOptions): Promise<void>
338
+ /**
339
+ * Adds a capability to a user account during a test.
340
+ *
341
+ * Use this method inside test scenarios instead of calling `await
342
+ * user.hasCapability()` directly to make this call part of the recorder
343
+ * queue, instead of executing it immediately during test step registration.
344
+ *
345
+ * @param capability
346
+ * The capability to be added to the user account.
347
+ *
348
+ * @param options
349
+ * Optional parameters.
350
+ */
351
+ haveCapability (capability: string, options?: UserOptions): Promise<void>
352
+ /**
353
+ * Removes a capability from a user account during a test.
354
+ *
355
+ * Use this method inside test scenarios instead of calling `await
356
+ * user.doesntHaveCapability()` directly to make this call part of the
357
+ * recorder queue, instead of executing it immediately during test step
358
+ * registration.
359
+ *
360
+ * @param capability
361
+ * The capability to be removed from the user account.
362
+ *
363
+ * @param options
364
+ * Optional parameters.
365
+ */
366
+ dontHaveCapability (capability: string, options?: UserOptions): Promise<void>
367
+ /**
368
+ * Changes the access combination of a user account during a test.
369
+ *
370
+ * Use this method inside test scenarios instead of calling `await
371
+ * user.hasAccessCombination()` directly to make this call part of the
372
+ * recorder queue, instead of executing it immediately during test step
373
+ * registration.
374
+ *
375
+ * @param accessCombination
376
+ * The access combination to be set at the user account.
377
+ *
378
+ * @param options
379
+ * Optional parameters.
380
+ */
381
+ haveAccessCombination (accessCombination: string, options?: UserOptions): Promise<void>
382
+ /**
383
+ * Changes the module access flags of a user account during a test.
384
+ *
385
+ * Use this method inside test scenarios instead of calling `await
386
+ * user.hasModuleAccess()` directly to make this call part of the recorder
387
+ * queue, instead of executing it immediately during test step
388
+ * registration.
389
+ *
390
+ * @param moduleAccess
391
+ * The module access flags to be set at the user account.
392
+ *
393
+ * @param options
394
+ * Optional parameters.
395
+ */
396
+ haveModuleAccess (moduleAccess: ModuleAccess, options?: UserOptions): Promise<void>
397
+ haveSnippet (snippet: object /* TODO */, options?: UserOptions): Promise<void>
398
+ haveMail (data: HaveMailData | HaveMailFromFile, options?: UserOptions): Promise<void>
399
+ haveMails (iterable: Iterable<HaveMailData | HaveMailFromFile>, options?: UserOptions): Promise<void>
400
+ /**
401
+ * Adds an alias email address to a user account during a test.
402
+ *
403
+ * Use this method inside test scenarios instead of calling `await
404
+ * user.hasAlias()` directly to make this call part of the recorder queue,
405
+ * instead of executing it immediately during test step registration.
406
+ *
407
+ * @param alias
408
+ * The alias email address to be added to the user account.
409
+ *
410
+ * @param options
411
+ * Optional parameters.
412
+ */
413
+ haveAnAlias (alias: string, options?: UserOptions): Promise<void>
414
+ /**
415
+ * Removes an alias email address from a user account during a test.
416
+ *
417
+ * Use this method inside test scenarios instead of calling `await
418
+ * user.doesntHaveAlias()` directly to make this call part of the recorder
419
+ * queue, instead of executing it immediately during test step registration.
420
+ *
421
+ * @param alias
422
+ * The alias email address to be removed from the user account.
423
+ *
424
+ * @param options
425
+ * Optional parameters.
426
+ */
427
+ dontHaveAlias (alias: string, options?: UserOptions): Promise<void>
428
+ haveMailFilterRule (rule: object /* TODO */, options?: UserOptions): Promise<void>
429
+ haveFolder (folder: { title: string; module: string; parent: string; permissions?: string[]; subscribed?: number }, options?: UserOptions): Promise<string>
430
+ haveContact (contact: object /* TODO */, options?: UserOptions): Promise<object> /* TODO */
431
+ grabDefaultFolder (module: string, options?: GrabDefaultFolderOptions): Promise<string>
432
+ /**
433
+ * Uploads a file to a specified folder.
434
+ * @param folderId - The ID of the folder where the file will be uploaded.
435
+ * @param file - The path to the file or an object containing the file information.
436
+ * @param options - Additional options for the file upload.
437
+ * @returns - The uploaded file data.
438
+ */
439
+ haveFile(folderId: string, file: string | HaveFileData, options?: HaveFileOptions): Promise<{ folder_id: string; id: string }>
440
+ haveTask (task: object /* TODO */, options?: UserOptions): Promise<object> /* TODO */
441
+ /**
442
+ * Uploads an attachment to the specified module and object.
443
+ * @param module - The module to attach the file to.
444
+ * @param obj - The object to attach the file to.
445
+ * @param file - The file to be attached.
446
+ * @param options - The options for creating the HTTP client.
447
+ */
448
+ haveAttachment (module: 'calendar' | 'chronos' | 'contacts' | 'drive' | 'files' | 'infostore' | 'tasks', obj: object /** TODO */, file: string, options?: UserOptions): Promise<object> /* TODO */
449
+ haveAppointment (appointment: object /* TODO */, options?: UserOptions): Promise<object> /* TODO */
450
+ importAppointment (data: { sourcePath: string; folder?: string }, options?: UserOptions): Promise<object> /* TODO */
451
+ haveResource (data: object /* TODO */, options?: UserOptions): Promise<string>
452
+ dontHaveResource (pattern: object /* TODO */, options?: UserOptions): Promise<Array<{ id: string; pattern: object }>>
453
+ haveGroup (group: object /* TODO */, options?: UserOptions): Promise<object> /* TODO */
454
+ dontHaveGroup (name: string, options?: UserOptions): Promise<Array<{ id: string; name: string }>>
455
+ haveLockedFile (data: object /* TODO */, options?: UserOptions): Promise<object> /* TODO */
456
+ setMailCategories (data: { mailId: string; folder: string; categories: string[] }, options?: UserOptions): Promise<object> /* TODO */
457
+ pressKeys (text: string): Promise<void>
458
+ /**
459
+ * @param options
460
+ * @param [options.user] a user object as returned by provisioning helper, default is the "first" user
461
+ * @param [options.additionalAccount] an additional user that will be provisioned as the external account
462
+ * @param [options.extension] optional extension added to the mail address ("ext" will be translated to: $user.primary+ext@mailDomain)
463
+ * @param [options.name] name of the account
464
+ * @param [options.transport_auth] transport authentication, default: 'none'
465
+ */
466
+ haveMailAccount (options?: { user?: object, additionalAccount?: object, extension?: string, name?: string, transport_auth?: string }): Promise<object> /* TODO */
467
+ waitForSetting (obj: object, timeout?: number, options?: UserOptions): Promise<void>
468
+ waitForCapability (capability: string, timeout?: number, options?: UserOptions & { shouldBe?: boolean }): Promise<void>
469
+ copyToClipboard (): Promise<void>
470
+ getClipboardContent (): Promise<string>
471
+ createGenericFile (filename: string, size: number): Promise<void>
472
+ }
473
+
474
+ export interface AppSuiteHelpers {
475
+ Playwright: CodeceptJS.Playwright
476
+ AppSuite: AppSuiteHelper
477
+ }
478
+
479
+ // actor --------------------------------------------------------------------
480
+
481
+ export interface LoginOptions extends UserOptions {
482
+ wait?: boolean
483
+ isDeepLink?: boolean
484
+ }
485
+
486
+ export interface AppSuiteActor {
487
+ amOnLoginPage (): void
488
+ openApp (appName: CodeceptJS.LocatorOrString): void
489
+ waitForApp (): void
490
+ /**
491
+ * This simplifies the input of mail addresses into input fields.
492
+ * There must be an array of users defined in the AppSuite helper configuration.
493
+ * If you want to fill in a mailaddress of such a user you can simply use this function.
494
+ *
495
+ * @param locator - the selector of an editable field
496
+ * @param userIndex - the users position in the users array provided via helper config
497
+ */
498
+ insertMailaddress (locator: CodeceptJS.LocatorOrString, userIndex: number): void
499
+ /**
500
+ * Logs in the user with the specified URL parameters and options.
501
+ * If the URL parameters are an object, it will be converted to an empty array.
502
+ * The options parameter is an object that can contain a 'user' property.
503
+ * If the 'user' property is not provided, the first user from the 'users' container will be used.
504
+ * The login process includes making an API request to the login endpoint with the user's credentials.
505
+ * After successful login, the page will be navigated to the specified URL with the URL parameters.
506
+ * If the 'isDeepLink' option is true, the URL will be constructed with a '#' prefix.
507
+ * The login process also includes waiting for the page to load and checking for the presence of the app.
508
+ * @param urlParams - The URL parameters as an array or object.
509
+ * @param options - The login options.
510
+ */
511
+ login(urlParams: string | string[], options?: LoginOptions): void
512
+ login(options?: LoginOptions): void
513
+ logout(): void
514
+ waitForNetworkTraffic (): void
515
+ /**
516
+ * Waits for the specified element to receive focus.
517
+ * @param selector - The locator of the element. Only accepts css selectors.
518
+ */
519
+ waitForFocus (selector: string): void
520
+ triggerRefresh (): void
521
+ grabBackgroundImageFrom (locator: CodeceptJS.LocatorOrString): Promise<string>
522
+ clickDropdown (text: string): void
523
+ clickToolbar (selector: string, timeout?: number): void
524
+ clickPrimary (text: string): void
525
+ openFolderMenu (folderName: string): void
526
+ changeTheme (options: { theme: string }): void
527
+ /**
528
+ * Selects the text in a text field or contenteditable element from cursor position to beginning of line.
529
+ */
530
+ selectLine (): void
531
+ }
532
+
533
+ export const actor: CodeceptJS.actor
534
+
535
+ // augment global CodeceptJS interfaces -------------------------------------
536
+
537
+ global {
538
+ namespace CodeceptJS {
539
+
540
+ // Playwright interface fixes
541
+ interface Playwright {
542
+ /** The browser object. */
543
+ readonly browser: Browser
544
+ /** The browser context object. */
545
+ readonly browserContext: BrowserContext
546
+ /** The page object for the current (active) page in the browser. */
547
+ readonly page: Page
548
+
549
+ // fix for non-generic `any` signature
550
+ usePlaywrightTo(description: string, callback: (playwright: Playwright) => void | Promise<void>): void
551
+ usePlaywrightTo<R>(description: string, callback: (playwright: Playwright) => R | Promise<R>): Promise<R>
552
+
553
+ // fix for non-generic `any` signature
554
+ executeScript(callback: (this: void) => void | Promise<void>): void
555
+ executeScript<T>(callback: (this: void, arg: T) => void | Promise<void>, arg: T): void
556
+ executeScript<R>(callback: (this: void) => R | Promise<R>): Promise<R>
557
+ executeScript<T, R>(callback: (this: void, arg: T) => R | Promise<R>, arg: T): Promise<R>
558
+
559
+ // fix for `any` callback signature
560
+ waitForRequest(urlOrPredicate: string | ((request: Request) => boolean), sec?: number): void
561
+ waitForResponse(urlOrPredicate: string | ((response: Response) => boolean), sec?: number): void
562
+ }
563
+
564
+ // helpers and actor methods for `I` object added by this plugin
565
+ interface Methods extends
566
+ SyncifyVoidMethods<Playwright>,
567
+ SyncifyVoidMethods<AppSuiteHelper>,
568
+ AppSuiteActor { }
569
+
570
+ // CodeceptJS does not add `Methods` to `I` by itself
571
+ interface I extends Methods { }
572
+
573
+ // additional fragments and page objects
574
+ interface SupportObject {
575
+ autocomplete: OXPO.ContactAutoCompleteFragment
576
+ calendar: OXPO.CalendarPageObject
577
+ contactpicker: OXPO.ContactPickerFragment
578
+ contacts: OXPO.ContactsPageObject
579
+ contexts: Contexts
580
+ dialogs: OXPO.DialogsFragment
581
+ drive: OXPO.DrivePageObject
582
+ mail: OXPO.MailPageObject
583
+ mailfilter: OXPO.SettingsMailFilterFragment
584
+ mobileCalendar: OXPO.MobileCalendarPageObject
585
+ mobileContacts: OXPO.MobileContactsPageObject
586
+ mobileMail: OXPO.MobileMailPageFragment
587
+ search: OXPO.SearchFragment
588
+ settings: OXPO.SettingsFragment
589
+ sharedaccounts: SharedAccounts
590
+ tasks: OXPO.TasksPageObject
591
+ tinymce: OXPO.TinyMceFragment
592
+ topbar: OXPO.TopBarFragment
593
+ users: Users
594
+ viewer: OXPO.ViewerFragment
595
+ }
596
+ }
597
+ }
598
+ }
package/index.js CHANGED
@@ -22,8 +22,9 @@ require('./src/chai')
22
22
  const { recorder, event: codeceptEvents } = require('codeceptjs')
23
23
  const pageobjects = require('@open-xchange/appsuite-codeceptjs-pageobjects')
24
24
 
25
- const dotenv = require('dotenv')
26
- dotenv.config({ path: ['.env', '.env.defaults'], quiet: true })
25
+ for (const envFile of ['.env', '.env.defaults']) {
26
+ try { process.loadEnvFile(envFile) } catch {}
27
+ }
27
28
 
28
29
  const outputDir = process.env.E2E_OUTPUT_DIR || './output'
29
30
 
@@ -52,20 +53,14 @@ module.exports = {
52
53
  browser: 'chromium',
53
54
  chromium: {
54
55
  args: [
55
- '--disable-print-preview',
56
- '--disable-crash-reporter',
57
- '--disable-dev-shm-usage',
58
- '--disable-features=IsolateOrigins',
59
56
  '--disable-gpu',
60
- '--disable-notifications', // to disable native notification window on Mac OS,
57
+ '--disable-notifications', // to disable native notification window on Mac OS
61
58
  '--disable-print-preview',
62
59
  '--disable-setuid-sandbox',
63
- '--disable-site-isolation-trials',
64
60
  '--disable-web-security',
65
- '--no-first-run',
66
- '--no-sandbox',
67
- '--no-zygote'
68
- ].concat((process.env.CHROME_ARGS || '').split(' '))
61
+ '--no-zygote',
62
+ '--renderer-process-limit=1'
63
+ ].concat((process.env.CHROME_ARGS || '').split(' ').filter(Boolean))
69
64
  },
70
65
  url: process.env.LAUNCH_URL,
71
66
  show: process.env.HEADLESS === 'false',
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@open-xchange/appsuite-codeceptjs",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "OX App Suite CodeceptJS Configuration and Helpers",
5
5
  "main": "index.js",
6
+ "types": "index.d.ts",
6
7
  "bin": {
7
8
  "e2e-rerun": "./customRerun.js"
8
9
  },
@@ -14,17 +15,14 @@
14
15
  "license": "AGPL-3.0-or-later",
15
16
  "private": false,
16
17
  "dependencies": {
17
- "@axe-core/playwright": "^4.11.0",
18
+ "@axe-core/playwright": "^4.11.1",
18
19
  "@codeceptjs/helper": "^2.0.4",
19
20
  "@influxdata/influxdb-client": "^1.35.0",
20
- "@open-xchange/appsuite-codeceptjs-pageobjects": "^1.1.0",
21
+ "@open-xchange/appsuite-codeceptjs-pageobjects": "^1.1.1",
21
22
  "@playwright/test": "1.58.2",
22
23
  "allure-codeceptjs": "2.15.1",
23
- "chai": "^6.2.1",
24
- "chalk": "^4.1.2",
25
- "chalk-table": "^1.0.2",
24
+ "chai": "^6.2.2",
26
25
  "codeceptjs": "3.7.6",
27
- "dotenv": "^17.2.3",
28
26
  "mocha": "^11.7.5",
29
27
  "mocha-junit-reporter": "^2.2.1",
30
28
  "mocha-multi": "^1.1.7",
@@ -33,17 +31,14 @@
33
31
  "p-retry": "^7.1.1",
34
32
  "playwright-core": "1.58.2",
35
33
  "short-uuid": "^6.0.3",
36
- "@open-xchange/soap-client": "0.0.12",
37
- "@open-xchange/codecept-horizontal-scaler": "0.1.14"
34
+ "@open-xchange/codecept-horizontal-scaler": "0.1.15",
35
+ "@open-xchange/soap-client": "0.1.0"
38
36
  },
39
37
  "devDependencies": {
40
- "@types/node": "^24.10.3",
38
+ "@types/node": "^25.5.0",
41
39
  "ts-node": "^10.9.2",
42
40
  "typescript": "^5.9.3",
43
- "@open-xchange/lint": "0.2.1"
44
- },
45
- "resolutions": {
46
- "axios": ">1.12.0"
41
+ "@open-xchange/lint": "0.3.0"
47
42
  },
48
43
  "scripts": {
49
44
  "lint": "eslint ."
@@ -21,8 +21,7 @@
21
21
  const { InfluxDB, Point } = require('@influxdata/influxdb-client')
22
22
  const event = require('codeceptjs/lib/event')
23
23
  const recorder = require('codeceptjs/lib/recorder')
24
- const chalk = require('chalk')
25
- const chalkTable = require('chalk-table')
24
+ const { styleText } = require('node:util')
26
25
 
27
26
  function durationOf (test) {
28
27
  return test.steps.reduce((sum, step) => sum + (step.endTime - step.startTime), 0)
@@ -39,18 +38,16 @@ module.exports = function setupTestMetricsPlugin ({ url, org, token, defaultTags
39
38
  writePoint (p) { points.push(p) },
40
39
  flush () {
41
40
  if (!points.length) return
42
- const options = {
43
- columns: [
44
- { field: 'measurement', name: chalk.cyan('Measurement') },
45
- { field: 'metric', name: chalk.cyan('Metric') },
46
- { field: 'duration', name: chalk.green('Duration') },
47
- { field: 'transferSize', name: chalk.green('Transfer size') },
48
- { field: 'decodedBodySize', name: chalk.green('Dec. body size') },
49
- { field: 'cached', name: chalk.yellow('Cached') },
50
- { field: 'tags', name: chalk.cyan('Tags') }
51
- ]
52
- }
53
- console.log(chalkTable(options, points.filter(p => p.name !== 'testrun').map(p => {
41
+ const columns = [
42
+ { field: 'measurement', name: 'Measurement', color: 'cyan' },
43
+ { field: 'metric', name: 'Metric', color: 'cyan' },
44
+ { field: 'duration', name: 'Duration', color: 'green' },
45
+ { field: 'transferSize', name: 'Transfer size', color: 'green' },
46
+ { field: 'decodedBodySize', name: 'Dec. body size', color: 'green' },
47
+ { field: 'cached', name: 'Cached', color: 'yellow' },
48
+ { field: 'tags', name: 'Tags', color: 'cyan' }
49
+ ]
50
+ const rows = points.filter(p => p.name !== 'testrun').map(p => {
54
51
  const metric = p.tags.metric
55
52
  delete p.tags.metric
56
53
  const cached = p.tags.cached
@@ -65,7 +62,11 @@ module.exports = function setupTestMetricsPlugin ({ url, org, token, defaultTags
65
62
  row[field] = p.fields[field]
66
63
  }
67
64
  return row
68
- })))
65
+ })
66
+ const widths = columns.map(col => Math.max(col.name.length, ...rows.map(r => String(r[col.field] ?? '').length)))
67
+ const header = columns.map((col, i) => styleText(col.color, col.name.padEnd(widths[i]))).join(' ')
68
+ const lines = rows.map(row => columns.map((col, i) => String(row[col.field] ?? '').padEnd(widths[i])).join(' '))
69
+ console.log([header, ...lines].join('\n'))
69
70
  points.splice(0, points.length)
70
71
  },
71
72
  dispose () {
@@ -1,9 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(find:*)",
5
- "Bash(grep:*)",
6
- "Bash(npm view:*)"
7
- ]
8
- }
9
- }
package/.env.defaults DELETED
@@ -1,48 +0,0 @@
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'
48
- DOTENV_CONFIG_QUIET=true
@@ -1 +0,0 @@
1
- FROM registry.gitlab.com/openxchange/base-images/wolfi/playwright:1.58.2
package/global.d.ts DELETED
@@ -1,3 +0,0 @@
1
- /* eslint-disable camelcase */
2
- declare const codecept_dir: string
3
- declare const output_dir: string
@@ -1,14 +0,0 @@
1
- packages:
2
- - ../lint
3
- - ../soap-client
4
- - ../codecept-horizontal-scaler
5
-
6
- ignoredBuiltDependencies:
7
- - detox
8
-
9
- onlyBuiltDependencies:
10
- - dtrace-provider
11
- - unrs-resolver
12
-
13
- publicHoistPattern:
14
- - '*eslint*'