/** * @copyright Copyright (c) Open-Xchange GmbH, Germany * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with OX App Suite. If not, see . * * Any use of the work other than as authorized under this license or copyright law is prohibited. */ declare module '@open-xchange/appsuite-codeceptjs' { // import global types of Chai and Mocha import 'chai' import 'mocha' import Helper from '@codeceptjs/helper' import { Page, Browser, BrowserContext, Request, Response, Cookie as PlaywrightCookie } from 'playwright-core' import * as OXPO from '@open-xchange/appsuite-codeceptjs-pageobjects' import { AxeBuilder } from '@axe-core/playwright' export { container, recorder, event as codeceptEvents } from 'codeceptjs' // chai --------------------------------------------------------------------- // from src/chai.js global { const assert: Chai.AssertStatic const expect: Chai.ExpectStatic namespace Chai { interface Assertion { accessible: Assertion } } } // util --------------------------------------------------------------------- /** * Converts the return type `Promise` of all methods in `T` to `void`. * * @template T * The interface with methods to be converted. */ export type SyncifyVoidMethods = { [K in keyof T]: T[K] extends (...args: infer A) => Promise ? R extends void ? (...args: A) => void : T[K] : T[K] } class PropagatedError extends Error { constructor (error: Error) } export type { PropagatedError } export const util: { getLoginTimeout (): number getURLRoot (): string getServerURL (): string getJavascriptRoot (): string userContextId (): number admin (): string mxDomain (): string smtpServer (): string imapServer (): string getDefaultUserPassword (): string PropagatedError: typeof PropagatedError addJitter (id: number): number /** * Resolves the effective user account to be used in a test. * * @param user * An optional explicit user account to be returned. * * @returns * The passed user account if existing, otherwise the first created user. */ resolveUser (user?: User): User } // config ------------------------------------------------------------------- export interface AppSuiteCodeceptConfig extends CodeceptJS.MainConfig { helpers: NonNullable include: NonNullable plugins: NonNullable } export const config: AppSuiteCodeceptConfig // contexts ----------------------------------------------------------------- export interface UserAttributes { entries: { key: string; value: unknown }[] } export interface ContextData { enabled: boolean filestoreId: number filestore_name: string id: number loginMappings: string[] maxQuota: number name: string userAttributes: UserAttributes } export interface AuthData { login: string password: string } export interface ContextAdmin extends AuthData { display_name: string email1: string given_name: string name: string primaryEmail: string sur_name: string } export type ModuleAccess = Record class Context { readonly id: number readonly ctxdata: ContextData readonly admin: ContextAdmin readonly auth: AuthData constructor (options: { ctxdata: ContextData; admin: ContextAdmin; auth: AuthData }) remove (): Promise hasConfig (key: string, value: unknown): Promise hasCapability (capability: string): Promise doesntHaveCapability (capability: string): Promise hasAccessCombination (accessCombinationName: string): Promise getModuleAccess (): Promise hasModuleAccess (moduleAccess: ModuleAccess): Promise hasQuota (maxQuota: number): Promise } export type { Context } export interface Contexts extends ReadonlyArray { create (ctx?: { filestoreId?: number; id?: string; maxQuota?: number }, adminUser?: ContextAdmin, auth?: AuthData): Promise reuse (ctx: Context, admin?: ContextAdmin, auth?: AuthData): Promise removeAll (auth?: AuthData): Promise } // user and shared accounts ------------------------------------------------- /** * Common attributes shared by user accounts and shared accounts. */ export interface CommonAccountData { aliases: string[] contextadmin: boolean convert_drive_user_folders: boolean defaultSenderAddress: string display_name: string email1: string given_name: string gui_spam_filter_enabled?: boolean id: number imapLogin: string imapPort: number imapSchema: string imapServer: string imapServerString: string language: string mailenabled: boolean name: string passwordMech: string primaryEmail: string smtpPort: number smtpSchema: string smtpServer: string smtpServerString: string sur_name: string userAttributes: UserAttributes } // user accounts ------------------------------------------------------------ export interface UserData extends CommonAccountData { password: string password_expired: boolean } class User { readonly userdata: UserData readonly context: Context readonly login: string constructor (options: { user: UserData, context: Context }) remove (): Promise hasConfig (key: string, value: unknown): Promise hasAlias (alias: string): Promise doesntHaveAlias (alias: string): Promise hasAccessCombination (accessCombinationName: string): Promise getModuleAccess (): Promise hasModuleAccess (moduleAccess: ModuleAccess): Promise hasCapability (capability: string): Promise doesntHaveCapability (capability: string): Promise get (param: K): UserData[K] toJSON (): UserData & { context: ContextData & { admin: ContextAdmin; auth: AuthData } } change (userdata: Partial): Promise } export type { User } export interface Users extends ReadonlyArray { getRandom (user?: Partial): Partial create (user?: Partial, context?: { id: number }): Promise removeAll (): Promise } // shared accounts ---------------------------------------------------------- export interface SharedAccountData extends CommonAccountData { maxQuota: number primaryAccountName: string timezone: string } export type SharedAccountCapability = 'sendAs' | 'sendOnBehalf' | 'snippets' | 'writeSnippets' | 'manageSieve' | 'writeJSlob' export interface SharedAccountCapabilitiesConfig { grantedCapability?: readonly SharedAccountCapability[] deniedCapability?: readonly SharedAccountCapability[] } export interface SharedAccountModuleConfig { permissionLevel?: 'viewer' | 'editor' | 'author' | 'admin' } export interface SharedAccountPermissions { /** The users to be granted access for a shared account. */ users?: User | readonly User[] /** Capabilities to be granted (string array), or configuration object with granted and denied capabilities. */ capabilities?: readonly SharedAccountCapability[] | SharedAccountCapabilitiesConfig /** Default configuration for all modules. */ moduleConfig?: SharedAccountModuleConfig /** Configuration for mail module. Will be merged with `moduleConfig`. */ mailConfig?: SharedAccountModuleConfig /** Configuration for calendar module. Will be merged with `moduleConfig`. */ calendarConfig?: SharedAccountModuleConfig } class SharedAccount { readonly sharedAccountData: SharedAccountData readonly context: Context constructor (options: { sharedAccountData: SharedAccountData, context: Context }) createPermissions (permissions: SharedAccountPermissions): Promise get (param: K): SharedAccountData[K] getFullId (): string getData (): Promise remove (): Promise toJSON (): SharedAccountData & { context: ContextData & { admin: ContextAdmin; auth: AuthData } } } export type { SharedAccount } export interface SharedAccounts extends ReadonlyArray { getRandom (): SharedAccountData convertFromUser (user: User, password: string): Promise create (sharedAccountData?: Partial, ctx?: Context): Promise list (context: Context): Promise removeAll (): Promise } // event -------------------------------------------------------------------- export interface AppSuiteCodeceptEventMap { 'provisioning.user.create': [user: UserData, context: Context] 'provisioning.user.created': [user: User] 'provisioning.user.removed': [user: User] 'provisioning.context.create': [context: ContextData, admin: ContextAdmin, auth: AuthData] 'provisioning.context.created': [context: Context] 'provisioning.context.removed': [context: Context] 'provisioning.sharedaccount.create': [sharedAccount: SharedAccountData, context: Context] 'provisioning.sharedaccount.created': [sharedAccount: SharedAccount] 'provisioning.sharedaccount.removed': [sharedAccount: SharedAccount] } export const event: { readonly provisioning: { readonly user: { readonly create: 'provisioning.user.create' readonly created: 'provisioning.user.created' readonly removed: 'provisioning.user.removed' } readonly context: { readonly create: 'provisioning.context.create' readonly created: 'provisioning.context.created' readonly removed: 'provisioning.context.removed' } readonly sharedaccount: { readonly create: 'provisioning.sharedaccount.create' readonly created: 'provisioning.sharedaccount.created' readonly removed: 'provisioning.sharedaccount.removed' } } dispatcher: NodeJS.EventEmitter emit (event: K, ...params: AppSuiteCodeceptEventMap[K]): void } // helper ------------------------------------------------------------------- export interface AxeReportOptions { disableRules?: string | string[] exclude?: string | string[] include?: string | string[] } // not exported directly from '@axe-core/playwright' export type AxeResults = Awaited> export interface UserOptions { /** * The user account to be used. Default is the first created user. * @default users[0] */ user?: User /** * Additional parameters that will be inserted into the query string of the * middleware API request URL. */ params?: Record } export interface FolderPermission { entity: number group: boolean bits: number } export interface FolderPermissionForUser { user: User access: 'viewer' | 'reviewer' | 'author' | 'administrator' | 'owner' } export interface HaveFolderData { title: string /** * The root module to create the folder in. The value 'event' corresponds * to calendar appointments. */ module: 'contacts' | 'event' | 'infostore' | 'mail' | 'tasks' /** * Parent folder identifier. Can be omitted to use the default folder of * the specified module: * - For module 'event' (calendar appointments), the default folder will be * the string `` `cal://0/${await I.grabDefaultFolder('calendar')}` ``. * - For all others modules, the default folder will be `await * I.grabDefaultFolder(module)`. */ parent?: string permissions?: (FolderPermission | FolderPermissionForUser)[] subscribed?: number } /** * The display name and mail address of a user. */ export type MailAddress = [name: string, address?: string] /** * Argument type for mail addresses, e.g. for mail attributes 'from', 'to', * and 'cc'. Can be one of: * - A single string to be used as mail address. * - A single user account or shared account to extract display name and mail * address from. * - An array of user accounts, shared accounts, and/or `MailAddress` pairs * with display name and mail address. */ export type MailAddresses = string | User | SharedAccount | readonly (MailAddress | User | SharedAccount)[] export interface MailAttachment { id?: string content_type?: string content?: string filename?: string size?: number disp?: 'inline' | 'attachment' | 'alternative' raw?: boolean } export interface MailSecurity { encrypt?: boolean sign?: boolean type?: 'pgp' | 'smime' } export interface HaveMailData { folder?: string from: MailAddresses to: MailAddresses cc?: MailAddresses subject?: string sendtype?: number content?: string flags?: number attachments?: (MailAttachment | string)[] security?: MailSecurity } export interface HaveMailFromFile { folder?: string path: string } export interface HaveMailFromSource { source: string filename?: string } /** Module identifiers for method `AppSuiteHelper::grabDefaultFolder()`. */ export type DefaultFolderModule = 'calendar' | 'contacts' | 'infostore' | 'mail' | 'tasks' export interface GrabDefaultFolderOptions extends UserOptions { /** * Type of default folder (for mail module only). * @default 'inbox' */ type?: string } export interface HaveFileDataBase { content?: BlobPart } export interface HaveFileData_Name extends HaveFileDataBase { name: string } export interface HaveFileData_Filename extends HaveFileDataBase { filename: string } export type HaveFileData = HaveFileData_Name | HaveFileData_Filename export interface HaveFileOptions extends UserOptions { cryptoAction?: 'encrypt' } export interface AppSuiteHelper extends Helper { waitForDownload (locator: string, filename: string, referenceFilePath?: string): Promise /** * Grabs and returns the accessibility report for the current page. */ grabAxeReport (options?: AxeReportOptions): Promise /** * Grabs and verifies the accessibility report for the current page. Prints * details for all found violations. */ verifyAxeReport (options?: AxeReportOptions): Promise selectFolder (id: string, context?: string): Promise throttleNetwork (networkConfig: 'OFFLINE' | 'GPRS' | '2G' | '3G' | '4G' | 'DSL' | 'ONLINE'): Promise haveSetting (...args: [settings: Record, options?: UserOptions] | [key: string, value: unknown, options?: UserOptions]): Promise /** * Changes a configuration item of a user account during a test. * * Use this method inside test scenarios instead of calling `await * user.hasConfig()` directly to make this call part of the recorder queue, * instead of executing it immediately during test step registration. * * @param key * The configuration key. * * @param value * The new configuration value. * * @param options * Optional parameters. */ haveConfig (key: string, value: unknown, options?: UserOptions): Promise /** * Adds a capability to a user account during a test. * * Use this method inside test scenarios instead of calling `await * user.hasCapability()` directly to make this call part of the recorder * queue, instead of executing it immediately during test step registration. * * @param capability * The capability to be added to the user account. * * @param options * Optional parameters. */ haveCapability (capability: string, options?: UserOptions): Promise /** * Removes a capability from a user account during a test. * * Use this method inside test scenarios instead of calling `await * user.doesntHaveCapability()` directly to make this call part of the * recorder queue, instead of executing it immediately during test step * registration. * * @param capability * The capability to be removed from the user account. * * @param options * Optional parameters. */ dontHaveCapability (capability: string, options?: UserOptions): Promise /** * Changes the access combination of a user account during a test. * * Use this method inside test scenarios instead of calling `await * user.hasAccessCombination()` directly to make this call part of the * recorder queue, instead of executing it immediately during test step * registration. * * @param accessCombination * The access combination to be set at the user account. * * @param options * Optional parameters. */ haveAccessCombination (accessCombination: string, options?: UserOptions): Promise /** * Changes the module access flags of a user account during a test. * * Use this method inside test scenarios instead of calling `await * user.hasModuleAccess()` directly to make this call part of the recorder * queue, instead of executing it immediately during test step * registration. * * @param moduleAccess * The module access flags to be set at the user account. * * @param options * Optional parameters. */ haveModuleAccess (moduleAccess: ModuleAccess, options?: UserOptions): Promise haveSnippet (snippet: object /* TODO */, options?: UserOptions): Promise haveMail (data: HaveMailData | HaveMailFromFile | HaveMailFromSource, options?: UserOptions): Promise haveMails (iterable: Iterable, options?: UserOptions): Promise /** * Adds an alias email address to a user account during a test. * * Use this method inside test scenarios instead of calling `await * user.hasAlias()` directly to make this call part of the recorder queue, * instead of executing it immediately during test step registration. * * @param alias * The alias email address to be added to the user account. * * @param options * Optional parameters. */ haveAnAlias (alias: string, options?: UserOptions): Promise /** * Removes an alias email address from a user account during a test. * * Use this method inside test scenarios instead of calling `await * user.doesntHaveAlias()` directly to make this call part of the recorder * queue, instead of executing it immediately during test step registration. * * @param alias * The alias email address to be removed from the user account. * * @param options * Optional parameters. */ dontHaveAlias (alias: string, options?: UserOptions): Promise haveMailFilterRule (rule: object /* TODO */, options?: UserOptions): Promise haveFolder (folder: HaveFolderData, options?: UserOptions): Promise haveContact (contact: object /* TODO */, options?: UserOptions): Promise /* TODO */ grabDefaultFolder (module: DefaultFolderModule, options?: GrabDefaultFolderOptions): Promise /** * Uploads a file to a specified folder. * @param folderId - The ID of the folder where the file will be uploaded, or `null` for default folder. * @param file - The path to the file or an object containing the file information. * @param options - Additional options for the file upload. * @returns - The uploaded file data. */ haveFile(folderId: string | null, file: string | HaveFileData, options?: HaveFileOptions): Promise<{ folder_id: string; id: string }> haveTask (task: object /* TODO */, options?: UserOptions): Promise /* TODO */ /** * Uploads an attachment to the specified module and object. * @param module - The module to attach the file to. * @param obj - The object to attach the file to. * @param file - The file to be attached. * @param options - The options for creating the HTTP client. */ haveAttachment (module: 'calendar' | 'chronos' | 'contacts' | 'drive' | 'files' | 'infostore' | 'tasks', obj: object /** TODO */, file: string, options?: UserOptions): Promise /* TODO */ haveAppointment (appointment: object /* TODO */, options?: UserOptions): Promise /* TODO */ importAppointment (data: { sourcePath: string; folder?: string }, options?: UserOptions): Promise /* TODO */ haveResource (data: object /* TODO */, options?: UserOptions): Promise dontHaveResource (pattern: string | object /* TODO */, options?: UserOptions): Promise<{ id: number; pattern: object }[]> haveGroup (group: object /* TODO */, options?: UserOptions): Promise /* TODO */ dontHaveGroup (name: string | RegExp, options?: UserOptions): Promise<{ id: string; name: string | RegExp }[]> haveLockedFile (data: object /* TODO */, options?: UserOptions): Promise /* TODO */ setMailCategories (data: { mailId: number; folder: string; categories: string[] }, options?: UserOptions): Promise /* TODO */ pressKeys (text: string): Promise /** * @param options * @param [options.user] a user object as returned by provisioning helper, default is the "first" user * @param [options.additionalAccount] an additional user that will be provisioned as the external account * @param [options.extension] optional extension added to the mail address ("ext" will be translated to: $user.primary+ext@mailDomain) * @param [options.name] name of the account * @param [options.transport_auth] transport authentication, default: 'none' */ haveMailAccount (options?: { user?: object, additionalAccount?: object, extension?: string, name?: string, transport_auth?: string }): Promise /* TODO */ waitForSetting (obj: object, timeout?: number, options?: UserOptions): Promise waitForCapability (capability: string, timeout?: number, options?: UserOptions & { shouldBe?: boolean }): Promise copyToClipboard (): Promise getClipboardContent (): Promise createGenericFile (filename: string, size: number): Promise } export interface AppSuiteHelpers { Playwright: CodeceptJS.Playwright AppSuite: AppSuiteHelper } // actor -------------------------------------------------------------------- export interface OpenAppOptions { wait?: boolean } export interface LoginOptions extends UserOptions, OpenAppOptions { isDeepLink?: boolean } export interface AppSuiteActor { amOnLoginPage (): void openApp (appName: CodeceptJS.LocatorOrString, options?: OpenAppOptions): void waitForApp (): void /** * This simplifies the input of mail addresses into input fields. * There must be an array of users defined in the AppSuite helper configuration. * If you want to fill in a mailaddress of such a user you can simply use this function. * * @param locator - the selector of an editable field * @param userIndex - the users position in the users array provided via helper config */ insertMailaddress (locator: CodeceptJS.LocatorOrString, userIndex: number): void /** * Logs in the user with the specified URL parameters and options. * If the URL parameters are an object, it will be converted to an empty array. * The options parameter is an object that can contain a 'user' property. * If the 'user' property is not provided, the first user from the 'users' container will be used. * The login process includes making an API request to the login endpoint with the user's credentials. * After successful login, the page will be navigated to the specified URL with the URL parameters. * If the 'isDeepLink' option is true, the URL will be constructed with a '#' prefix. * The login process also includes waiting for the page to load and checking for the presence of the app. * @param urlParams - The URL parameters as an array or object. * @param options - The login options. */ login(urlParams: string | string[], options?: LoginOptions): void login(options?: LoginOptions): void logout(): void waitForNetworkTraffic (): void /** * Waits for the specified element to receive focus. * @param selector - The locator of the element. Only accepts css selectors. * @param {number} secs - Timeout in seconds. */ waitForFocus (selector: string, secs?: number): void triggerRefresh (): void grabBackgroundImageFrom (locator: CodeceptJS.LocatorOrString): Promise clickDropdown (text: string): void clickToolbar (selector: string, timeout?: number): void clickPrimary (text: string): void openFolderMenu (folderName: string): void changeTheme (options: { theme: string }): void /** * Selects the text in a text field or contenteditable element from cursor position to beginning of line. */ selectLine (): void } export const actor: CodeceptJS.actor // augment global CodeceptJS interfaces ------------------------------------- global { namespace CodeceptJS { interface BoundRect { x: number; y: number; width: number; height: number; } // Playwright interface fixes interface Playwright { /** The browser object. */ readonly browser: Browser /** The browser context object. */ readonly browserContext: BrowserContext /** The page object for the current (active) page in the browser. */ readonly page: Page // fix for non-generic `any` signature usePlaywrightTo(description: string, callback: (playwright: Playwright) => void | Promise): void usePlaywrightTo(description: string, callback: (playwright: Playwright) => R | Promise): Promise // fix for non-generic `any` signature executeScript(callback: (this: void) => void | Promise): void executeScript(callback: (this: void, arg: T) => void | Promise, arg: T): void executeScript(callback: (this: void) => R | Promise): Promise executeScript(callback: (this: void, arg: T) => R | Promise, arg: T): Promise // fix for `any` callback signature waitForRequest(urlOrPredicate: string | ((request: Request) => boolean), sec?: number): void waitForResponse(urlOrPredicate: string | ((response: Response) => boolean), sec?: number): void // fix for `any` return type grabCookie(): PlaywrightCookie[]; grabCookie(name: string): PlaywrightCookie | undefined; // overload for correct return types grabElementBoundingRect(locator: LocatorOrString): Promise grabElementBoundingRect(locator: LocatorOrString, prop: K): Promise } // helpers and actor methods for `I` object added by this plugin interface Methods extends SyncifyVoidMethods, SyncifyVoidMethods, AppSuiteActor { } // CodeceptJS does not add `Methods` to `I` by itself interface I extends Methods { } // additional fragments and page objects interface SupportObject { autocomplete: OXPO.ContactAutoCompleteFragment calendar: OXPO.CalendarPageObject contactpicker: OXPO.ContactPickerFragment contacts: OXPO.ContactsPageObject contexts: Contexts dialogs: OXPO.DialogsFragment drive: OXPO.DrivePageObject mail: OXPO.MailPageObject mailfilter: OXPO.SettingsMailFilterFragment mobileCalendar: OXPO.MobileCalendarPageObject mobileContacts: OXPO.MobileContactsPageObject mobileMail: OXPO.MobileMailPageFragment search: OXPO.SearchFragment settings: OXPO.SettingsFragment sharedaccounts: SharedAccounts tasks: OXPO.TasksPageObject tinymce: OXPO.TinyMceFragment topbar: OXPO.TopBarFragment users: Users viewer: OXPO.ViewerFragment } interface Suite { suites: Suite[] tests: Test[] eachTest (fn: (test: Test) => void): this } interface Test { parent: Suite } } } }