wdio-obsidian-service 2.1.2 → 2.1.4

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/README.md CHANGED
@@ -199,6 +199,9 @@ it("test the thing", async function() {
199
199
 
200
200
  Testing on iOS is currently not supported.
201
201
 
202
+ ### MacOS
203
+ MacOS security settings will sometimes block the downloaded Obsidian executables from running with errors like `Obsidian is damaged and can't be opened. This file was downloaded on an unknown date`. You can work around this by setting the executable as allowed in "Privacy & Security" settings. See this [comment on issue #46](https://github.com/jesse-r-s-hines/wdio-obsidian-service/issues/46#issuecomment-3419205737) for instructions.
204
+
202
205
  ### Mobile Emulation
203
206
 
204
207
  Testing your plugin with "mobile emulation" is very easy to set up. Just add a capability like this in your `wdio.conf.mts`:
@@ -288,18 +291,19 @@ See also: The [sample plugin](https://github.com/jesse-r-s-hines/wdio-obsidian-s
288
291
 
289
292
  WebdriverIO can run tests using [Mocha](https://mochajs.org), [Jasmine](https://jasmine.github.io), and [Cucumber](https://cucumber.io/). Mocha is the easiest to set up and is used in all the `wdio-obsidian-service` examples. Mocha can also run your unit tests, typically with the addition of an assertion library like [Chai](https://www.chaijs.com). You can't run WebdriverIO using [Jest](https://jestjs.io), but if you already have Jest unit tests (or just prefer Jest) you can easily continue using Jest for your unit tests and Mocha just for your e2e tests. The built-in WebdriverIO [expect](https://webdriver.io/docs/api/expect-webdriverio) is very similar to Jest matchers, so should be familiar to use.
290
293
 
291
- ### API Docs
294
+ ## API Docs
292
295
 
293
296
  API docs, including all configuration options and helper functions, are available [here](https://jesse-r-s-hines.github.io/wdio-obsidian-service/wdio-obsidian-service/README.html).
294
297
 
295
298
  Some key bits:
296
299
  - See [ObsidianCapabilityOptions](https://jesse-r-s-hines.github.io/wdio-obsidian-service/wdio-obsidian-service/ObsidianCapabilityOptions.html) for all the options you can pass to `wdio:obsidianOptions` in your wdio.conf.mts
297
300
  - See [ObsidianBrowserCommands](https://jesse-r-s-hines.github.io/wdio-obsidian-service/wdio-obsidian-service/ObsidianBrowserCommands.html) and [ObsidianPage](https://jesse-r-s-hines.github.io/wdio-obsidian-service/wdio-obsidian-service/ObsidianPage.html) for various useful helper functions
301
+ - See [startWdioSession](https://jesse-r-s-hines.github.io/wdio-obsidian-service/wdio-obsidian-service/startWdioSession.html) if you aren't making e2e tests but want to use WDIO for scripts that interact with Obsidian
298
302
 
299
303
  And of course, see also [WDIO's documentation](https://webdriver.io/docs/gettingstarted) and the [many browser commands it provides](https://webdriver.io/docs/api/browser).
300
304
 
301
- ### GitHub CI Workflows
305
+ ## GitHub CI Workflows
302
306
  The sample plugin has workflows set up to release and test your plugin, which you can see [here](https://github.com/jesse-r-s-hines/wdio-obsidian-service-sample-plugin#github-workflows).
303
307
 
304
- ### obsidian-launcher CLI
308
+ ## obsidian-launcher CLI
305
309
  `wdio-obsidian-service` depends on [obsidian-launcher](../../packages/obsidian-launcher/README.md) so the `obsidian-launcher` CLI is also available, with some commands for launching different Obsidian versions. CLI docs available [here](https://jesse-r-s-hines.github.io/wdio-obsidian-service/obsidian-launcher/README.html#cli).
package/dist/index.d.ts CHANGED
@@ -570,6 +570,24 @@ declare class ObsidianWorkerService implements Services.ServiceInstance {
570
570
  *
571
571
  * See also: https://webdriver.io/docs/setuptypes/#standalone-mode
572
572
  *
573
+ * Example:
574
+ * ```ts
575
+ * const browser = await startWdioSession({
576
+ * capabilities: {
577
+ * browserName: "obsidian",
578
+ * browserVersion: "latest",
579
+ * 'wdio:obsidianOptions': {
580
+ * installerVersion: "latest",
581
+ * vault: "./test/vaults/basic",
582
+ * },
583
+ * },
584
+ * });
585
+ * await browser.executeObsidian(({app}) => {
586
+ * // extract some file metadata, edit the vault, etc...
587
+ * });
588
+ * await browser?.deleteSession();
589
+ * ```
590
+ *
573
591
  * @category WDIO Helpers
574
592
  */
575
593
  declare function startWdioSession(params: Capabilities.WebdriverIOConfig, serviceOptions?: ObsidianServiceOptions): Promise<WebdriverIO.Browser>;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/service.ts","../src/pageobjects/obsidianPage.ts","../src/types.ts","../../../node_modules/.pnpm/@wdio+globals@9.17.0_expect-webdriverio@5.4.1_webdriverio@9.18.4/node_modules/@wdio/globals/build/index.js","../src/pageobjects/basePage.ts","../src/utils.ts","../src/browserCommands.ts","../src/standalone.ts"],"sourcesContent":["/**\n * @module\n * @document ../README.md\n * @categoryDescription Options\n * Capability and service options.\n * @categoryDescription WDIO Helpers\n * Helpers for use in wdio.conf.mts, or for launching WDIO in standalone mode.\n * @categoryDescription Utilities\n * Browser commands and helper functions for writing tests.\n */\nimport ObsidianLauncher from \"obsidian-launcher\";\nimport { deprecate } from \"util\";\n\nimport { ObsidianLauncherService, ObsidianWorkerService } from \"./service.js\";\n/** @hidden */\nexport default ObsidianWorkerService;\n/** @hidden */\nexport const launcher = ObsidianLauncherService;\n\nexport type { ObsidianCapabilityOptions, ObsidianServiceOptions } from \"./types.js\";\nexport type { ObsidianBrowserCommands, ExecuteObsidianArg, InstalledPlugins } from \"./browserCommands.js\";\nexport { default as obsidianPage } from \"./pageobjects/obsidianPage.js\";\nexport type { ObsidianPage, Platform } from \"./pageobjects/obsidianPage.js\";\nexport type { PluginEntry, ThemeEntry } from \"obsidian-launcher\";\n\nexport { minSupportedObsidianVersion } from \"./service.js\";\nexport { startWdioSession } from \"./standalone.js\";\n\n// Some convenience helpers for use in wdio.conf.mts\n\n/**\n * Returns true if there is a current Obsidian beta and we have the credentials to download it, or its already in cache.\n * @category WDIO Helpers\n * @param opts.cacheDir Obsidian cache dir, defaults to `.obsidian-cache`.\n */\nexport async function obsidianBetaAvailable(opts: string|{cacheDir?: string} = {}) {\n opts = typeof opts == \"string\" ? {cacheDir: opts} : opts;\n const launcher = new ObsidianLauncher(opts);\n const versionInfo = await launcher.getVersionInfo(\"latest-beta\");\n return versionInfo.isBeta && await launcher.isAvailable(versionInfo.version);\n}\n\n/**\n * Resolves Obsidian app and installer version strings to absolute versions.\n * \n * @category WDIO Helpers\n * @deprecated Use parseObsidianVersions instead\n */\nexport const resolveObsidianVersions = deprecate(async function(\n appVersion: string, installerVersion: string, cacheDir?: string,\n): Promise<[string, string]> {\n const launcher = new ObsidianLauncher({cacheDir: cacheDir});\n return await launcher.resolveVersion(appVersion, installerVersion);\n}, 'resolveObsidianVersions is deprecated, use parseObsidianVersions instead');\n\n\n/**\n * Parses a string of Obsidian versions into [appVersion, installerVersion] tuples. This is a convenience helper for use\n * in `wdio.conf.mts`\n * \n * `versions` should be a space separated list of Obsidian app versions. You can optionally specify the installer\n * version by using \"appVersion/installerVersion\" e.g. `\"1.7.7/1.8.10\"`.\n * \n * Example: \n * ```js\n * parseObsidianVersions(\"1.8.10/1.7.7 latest latest-beta/earliest\")\n * ```\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n * \n * @category WDIO Helpers\n * @param versions string to parse\n * @param opts.cacheDir Obsidian cache dir, defaults to `.obsidian-cache`.\n * @returns [appVersion, installerVersion][] resolved to specific versions.\n */\nexport async function parseObsidianVersions(\n versions: string,\n opts: {cacheDir?: string} = {},\n): Promise<[string, string][]> {\n const launcher = new ObsidianLauncher({cacheDir: opts.cacheDir});\n return launcher.parseVersions(versions);\n}\n","import fs from \"fs\"\nimport fsAsync from \"fs/promises\"\nimport path from \"path\"\nimport { SevereServiceError } from 'webdriverio'\nimport type { Capabilities, Options, Services } from '@wdio/types'\nimport logger from '@wdio/logger'\nimport { fileURLToPath } from \"url\"\nimport ObsidianLauncher, {\n PluginEntry, ThemeEntry, DownloadedPluginEntry, DownloadedThemeEntry,\n} from \"obsidian-launcher\"\nimport { browserCommands } from \"./browserCommands.js\"\nimport {\n ObsidianServiceOptions, NormalizedObsidianCapabilityOptions, OBSIDIAN_CAPABILITY_KEY,\n} from \"./types.js\"\nimport {\n isAppium, appiumUploadFiles, appiumDownloadFile, getAppiumOptions, fileExists,\n} from \"./utils.js\";\nimport semver from \"semver\"\nimport _ from \"lodash\"\n\n\nconst log = logger(\"wdio-obsidian-service\");\n\nfunction getDefaultCacheDir(rootDir: string) {\n return path.resolve(rootDir, process.env.WEBDRIVER_CACHE_DIR ?? process.env.OBSIDIAN_CACHE ?? \"./.obsidian-cache\");\n}\n\n/** By default wdio continues on service errors, so we throw a SevereServiceError to make it bail on error */\nfunction getServiceErrorMessage(e: any) {\n return (\n `Failed to download and setup Obsidian. Caused by:\\n` +\n `${e.stack}\\n`+\n ` ------The above causes:-----`\n );\n}\n\nfunction resolveEntry(rootDir: string, entry: PluginEntry|ThemeEntry): PluginEntry|ThemeEntry {\n if (typeof entry == \"string\") {\n return path.resolve(rootDir, entry);\n } else if ('path' in entry) {\n return {...entry, path: path.resolve(rootDir, entry.path)};\n } else {\n return entry;\n }\n}\n\n/** Returns a plugin list with only the selected plugin ids enabled. */\nfunction selectPlugins(currentPlugins: DownloadedPluginEntry[], selection?: string[]): DownloadedPluginEntry[] {\n if (selection !== undefined) {\n const unknownPlugins = _.difference(selection, currentPlugins.map(p => p.id));\n if (unknownPlugins.length > 0) {\n throw Error(`Unknown plugin ids: ${unknownPlugins.join(', ')}`)\n }\n return currentPlugins.map(p => ({\n ...p,\n enabled: selection.includes(p.id) || p.id == \"wdio-obsidian-service-plugin\",\n }));\n } else {\n return currentPlugins;\n }\n}\n\n/** Returns a theme list with only the selected theme enabled. */\nfunction selectThemes(currentThemes: DownloadedThemeEntry[], selection?: string): DownloadedThemeEntry[] {\n if (selection !== undefined) {\n if (selection != \"default\" && currentThemes.every(t => t.name != selection)) {\n throw Error(`Unknown theme: ${selection}`);\n }\n return currentThemes.map(t => ({...t, enabled: selection != 'default' && t.name === selection}));\n } else {\n return currentThemes;\n }\n}\n\nfunction getNormalizedObsidianOptions(cap: WebdriverIO.Capabilities): NormalizedObsidianCapabilityOptions {\n return cap[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n}\n\n\n/**\n * Minimum Obsidian version that wdio-obsidian-service supports.\n * @category WDIO Helpers\n */\nexport const minSupportedObsidianVersion: string = \"1.0.3\"\n\n\n/**\n * wdio launcher service.\n * Use in wdio.conf.mts like so:\n * ```ts\n * services: ['obsidian'],\n * ```\n * @hidden\n */\nexport class ObsidianLauncherService implements Services.ServiceInstance {\n private obsidianLauncher: ObsidianLauncher\n private readonly helperPluginPath: string\n private readonly rootDir: string\n\n constructor (\n public options: ObsidianServiceOptions,\n public capabilities: WebdriverIO.Capabilities,\n public config: Options.Testrunner\n ) {\n this.rootDir = config.rootDir || process.cwd();\n this.obsidianLauncher = new ObsidianLauncher({\n cacheDir: config.cacheDir ?? getDefaultCacheDir(this.rootDir),\n versionsUrl: options.versionsUrl,\n communityPluginsUrl: options.communityPluginsUrl, communityThemesUrl: options.communityThemesUrl,\n });\n this.helperPluginPath = path.resolve(fileURLToPath(import.meta.url), '../../helper-plugin');\n }\n\n /**\n * Validates wdio:obsidianOptions and downloads Obsidian, plugins, and themes.\n */\n async onPrepare(config: Options.Testrunner, capabilities: Capabilities.TestrunnerCapabilities) {\n try {\n if (!Array.isArray(capabilities)) {\n capabilities = Object.values(capabilities).map(\n (multiremoteOption) => (multiremoteOption as Capabilities.WithRequestedCapabilities).capabilities,\n );\n }\n\n const obsidianCapabilities = capabilities.flatMap((cap) => {\n if ((\"browserName\" in cap) && cap.browserName === \"obsidian\") {\n return [cap as WebdriverIO.Capabilities];\n } else {\n return [];\n }\n });\n\n for (const cap of obsidianCapabilities) {\n const obsidianOptions = cap[OBSIDIAN_CAPABILITY_KEY] ?? {};\n \n // check vault\n const vault = obsidianOptions.vault ? path.resolve(this.rootDir, obsidianOptions.vault) : undefined;\n if (vault && !fs.existsSync(vault)) {\n throw Error(`Vault \"${vault}\" doesn't exist`)\n }\n \n // download plugins and themes to cache\n const plugins = await this.obsidianLauncher.downloadPlugins(\n (obsidianOptions.plugins ?? [])\n .concat([this.helperPluginPath]) // Always install the helper plugin\n .map(p => resolveEntry(this.rootDir, p) as PluginEntry)\n );\n\n const themes = await this.obsidianLauncher.downloadThemes(\n (obsidianOptions.themes ?? [])\n .map(t => resolveEntry(this.rootDir, t) as ThemeEntry),\n );\n\n let appVersion = cap.browserVersion ?? cap[OBSIDIAN_CAPABILITY_KEY]?.appVersion ?? \"latest\";\n appVersion = (await this.obsidianLauncher.getVersionInfo(appVersion)).version;\n if (semver.lt(appVersion, minSupportedObsidianVersion)) {\n throw Error(`Minimum supported Obsidian version is ${minSupportedObsidianVersion}`)\n }\n\n if (isAppium(cap)) {\n let apk = getAppiumOptions(cap).app;\n if (!apk) {\n apk = await this.obsidianLauncher.downloadAndroid(appVersion);\n }\n let chromedriverDir = getAppiumOptions(cap).chromedriverExecutableDir;\n if (!chromedriverDir) {\n chromedriverDir = path.join(this.obsidianLauncher.cacheDir, 'appium-chromedriver');\n }\n\n const normalizedObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...obsidianOptions,\n plugins, themes, vault: vault,\n appVersion, installerVersion: appVersion,\n emulateMobile: false,\n }\n cap[OBSIDIAN_CAPABILITY_KEY] = normalizedObsidianOptions;\n cap['appium:app'] = apk;\n cap['appium:chromedriverExecutableDir'] = chromedriverDir;\n cap[\"wdio:enforceWebDriverClassic\"] = true; // BiDi doesn't seem to work on Obsidian mobile\n if (!getAppiumOptions(cap)['noReset']) {\n console.warn(\"Note: For best performance set noReset to true, wdio-obsidian-service will handle resetting Obsidian between tests.\")\n }\n } else {\n const [, installerVersion] = await this.obsidianLauncher.resolveVersion(\n appVersion, obsidianOptions.installerVersion ?? \"earliest\",\n );\n const installerInfo = await this.obsidianLauncher.getInstallerInfo(installerVersion);\n\n let installerPath: string;\n if (obsidianOptions.binaryPath) {\n installerPath = path.resolve(this.rootDir, obsidianOptions.binaryPath)\n } else {\n installerPath = await this.obsidianLauncher.downloadInstaller(installerVersion);\n }\n let appPath: string;\n if (obsidianOptions.appPath) {\n appPath = path.resolve(this.rootDir, obsidianOptions.appPath)\n } else {\n appPath = await this.obsidianLauncher.downloadApp(appVersion);\n }\n let chromedriverPath = cap['wdio:chromedriverOptions']?.binary;\n // wdio can't download chromedriver for versions less than 115 automatically. Fetching it ourselves is\n // also a bit faster as it skips the chromedriver version detection step.\n if (!chromedriverPath) {\n chromedriverPath = await this.obsidianLauncher.downloadChromedriver(installerVersion);\n }\n\n const normalizedObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...obsidianOptions,\n plugins, themes, vault,\n binaryPath: installerPath, appPath: appPath,\n appVersion, installerVersion,\n emulateMobile: obsidianOptions.emulateMobile ?? false,\n }\n\n cap.browserName = \"chrome\";\n cap.browserVersion = installerInfo.chrome;\n cap[OBSIDIAN_CAPABILITY_KEY] = normalizedObsidianOptions;\n cap['goog:chromeOptions'] = {\n binary: installerPath,\n windowTypes: [\"app\", \"webview\"],\n ...cap['goog:chromeOptions'],\n args: [\n // Workaround for SUID issue on linux. See https://github.com/electron/electron/issues/42510\n ...(process.platform == 'linux' ? [\"--no-sandbox\"] : []),\n ...(cap['goog:chromeOptions']?.args ?? [])\n ],\n }\n cap['wdio:chromedriverOptions'] = {\n // allowedIps is not included in the types, but gets passed as --allowed-ips to chromedriver.\n // It defaults to [\"0.0.0.0\"] which makes Windows Firewall complain, and we don't need remote\n // connections anyways.\n allowedIps: [],\n ...cap['wdio:chromedriverOptions'],\n binary: chromedriverPath,\n } as WebdriverIO.ChromedriverOptions;\n cap[\"wdio:enforceWebDriverClassic\"] = true; // electron doesn't support BiDi yet.\n }\n }\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n}\n\n\n/**\n * wdio worker service.\n * Use in wdio.conf.mts like so:\n * ```ts\n * services: ['obsidian'],\n * ```\n * @hidden\n */\nexport class ObsidianWorkerService implements Services.ServiceInstance {\n private obsidianLauncher: ObsidianLauncher\n private browser: WebdriverIO.Browser|undefined;\n /** Directories to clean up after the tests */\n private tmpDirs: string[]\n /** Path on Android devices to store temporary vaults */\n private androidVaultDir = \"/storage/emulated/0/Documents/wdio-obsidian-service-vaults\";\n\n constructor (\n public options: ObsidianServiceOptions,\n public capabilities: WebdriverIO.Capabilities,\n public config: Options.Testrunner\n ) {\n this.obsidianLauncher = new ObsidianLauncher({\n cacheDir: config.cacheDir ?? getDefaultCacheDir(config.rootDir || process.cwd()),\n versionsUrl: options.versionsUrl,\n communityPluginsUrl: options.communityPluginsUrl, communityThemesUrl: options.communityThemesUrl,\n });\n this.tmpDirs = [];\n }\n\n /**\n * Creates a copy of the vault with plugins and themes installed\n */\n private async setupVault(cap: WebdriverIO.Capabilities) {\n const obsidianOptions = getNormalizedObsidianOptions(cap);\n let vaultCopy: string|undefined;\n if (obsidianOptions.vault != undefined) {\n log.info(`Opening vault ${obsidianOptions.vault}`);\n vaultCopy = await this.obsidianLauncher.setupVault({\n vault: obsidianOptions.vault,\n copy: true,\n plugins: obsidianOptions.plugins,\n themes: obsidianOptions.themes,\n });\n this.tmpDirs.push(vaultCopy);\n } else {\n log.info(`Opening Obsidian without a vault`)\n }\n obsidianOptions.vaultCopy = vaultCopy; // for use in getVaultPath() and the other service hooks\n }\n\n /**\n * Sets up the --user-data-dir for the Electron app. Sets the obsidian.json in the dir to open the vault on boot.\n */\n private async electronSetupConfigDir(cap: WebdriverIO.Capabilities) {\n const obsidianOptions = getNormalizedObsidianOptions(cap);\n const configDir = await this.obsidianLauncher.setupConfigDir({\n appVersion: obsidianOptions.appVersion, installerVersion: obsidianOptions.installerVersion,\n appPath: obsidianOptions.appPath,\n vault: obsidianOptions.vaultCopy,\n // `app.emulateMobile` just sets this localStorage variable. Setting it ourselves here instead of calling\n // the function simplifies the boot/plugin load sequence and makes sure plugins load in mobile mode.\n localStorage: obsidianOptions.emulateMobile ? {\"EmulateMobile\": \"1\"} : {},\n });\n this.tmpDirs.push(configDir);\n\n cap['goog:chromeOptions'] = {\n ...cap['goog:chromeOptions'],\n args: [\n `--user-data-dir=${configDir}`,\n ...(cap['goog:chromeOptions']?.args ?? []).filter(arg => {\n const match = arg.match(/^--user-data-dir=(.*)$/);\n return !match || !this.tmpDirs.includes(match[1]);\n })\n ]\n }\n }\n\n /**\n * Sets up the Obsidian app. Installs and launches it if needed, and sets permissions and contexts.\n * \n * You can configure Appium to install and launch the app for you. However, if you do that it reboots the app after\n * every session/spec which is really slow. So we are handling the app setup manually here.\n */\n private async appiumSetupApp() {\n const browser = this.browser!;\n const appiumOptions = getAppiumOptions(browser.requestedCapabilities);\n const obsidianOptions = getNormalizedObsidianOptions(browser.requestedCapabilities);\n const appId = \"md.obsidian\";\n\n // install/reinstall Obsidian if needed\n const dumpsys: string = await browser.execute(\"mobile: shell\", {command: \"dumpsys\", args: [\"package\", appId]});\n const installedObsidianVersion = dumpsys.match(/versionName[=:](.*)$/m)?.[1]?.trim();\n if (installedObsidianVersion != obsidianOptions.appVersion) {\n await browser.execute('mobile: terminateApp', {appId}); // terminate app (if running)\n await browser.execute('mobile: removeApp', {appId, keepData: false}); // uninstall (if present)\n await browser.execute('mobile: installApp', {\n appPath: appiumOptions.app, // the APK\n timeout: appiumOptions.androidInstallTimeout, // respect appium configuration\n grantPermissions: true, // this and autoGrantPermissions don't seem to really work, see below\n });\n }\n\n // start app if needed\n const appState: number = await browser.execute(\"mobile: queryAppState\", {appId});\n if (appState < 4) { // 0: not installed, 1: not running, 3: running in background, 4: running in foreground\n await browser.execute(\"mobile: activateApp\", {appId});\n }\n\n // grant Obsidian the permissions it needs, mainly file access.\n // appium:autoGrantPermissions is supposed to automatically grant everything it needs, but I can't get it to\n // work. The \"mobile: changePermissions\" \"all\" option also doesn't seem to work here. I have to explicitly list\n // the permissions and use the \"appops\" target. See https://github.com/appium/appium/issues/19991\n // I'm calling \"mobile: changePermissions\" \"all\" as well just in case it is actually doing something\n await browser.execute(\"mobile: changePermissions\", {\n action: \"grant\",\n appPackage: appId,\n permissions: \"all\",\n });\n await browser.execute(\"mobile: changePermissions\", {\n action: \"allow\",\n appPackage: appId,\n permissions: [ // these are from apk AndroidManifest.xml (extracted with apktool)\n \"READ_EXTERNAL_STORAGE\", \"WRITE_EXTERNAL_STORAGE\", \"MANAGE_EXTERNAL_STORAGE\",\n ],\n target: \"appops\", // requires appium --allow-insecure adb_shell\n });\n\n // switch to the webview context\n const context = \"WEBVIEW_md.obsidian\";\n await browser.waitUntil(async () => (await browser.getContexts() as string[]).includes(context));\n await browser.switchContext(context);\n\n // Clear app state\n await browser.execute(() => { // in case tests get interrupted during the `_capacitor_file_` bit\n if (window.location.href != \"http://localhost/\") {\n window.location.replace('http://localhost/');\n }\n })\n if (!obsidianOptions.vault) { // skip if vault is set, as we'll clear stat when we open the new vault\n await this.appiumCloseVault();\n }\n }\n\n /**\n * Opens the vault in appium.\n */\n private async appiumOpenVault() {\n const browser = this.browser!;\n const obsidianOptions = getNormalizedObsidianOptions(browser.requestedCapabilities);\n const androidVault = `${this.androidVaultDir}/${path.basename(obsidianOptions.vaultCopy!)}`;\n // TODO: Capabilities is not really the right place to be storing state like vaultCopy and uploadVault\n obsidianOptions.uploadedVault = androidVault;\n // transfer the vault to the device\n await appiumUploadFiles(browser, {src: obsidianOptions.vaultCopy!, dest: androidVault});\n\n // open vault by setting the localStorage keys and relaunching Obsidian\n // on appium restarting the app with appium:fullReset is *really* slow. And, unlike electron we can actually\n // switch vault with just a reload. So for Appium, instead of rebooting we manually wipe localStorage and use\n // reload to switch the vault.\n await browser.execute(async (androidVault) => {\n localStorage.clear();\n localStorage.setItem('mobile-external-vaults', JSON.stringify([androidVault]));\n localStorage.setItem('mobile-selected-vault', androidVault);\n // appId on mobile is just the full vault path\n localStorage.setItem(`enable-plugin-${androidVault}`, 'true');\n window.location.reload();\n }, androidVault);\n }\n\n /**\n * Close any open vault and go back to the vault switcher\n */\n private async appiumCloseVault() {\n const browser = this.browser!;\n await browser.execute(async () => {\n if (localStorage.length > 0) { // skip if we're already on the vault switcher\n localStorage.clear();\n location.reload();\n }\n });\n }\n\n /**\n * Waits for Obsidian to be ready, and does some other final setup.\n */\n private async prepareApp() {\n const browser = this.browser!;\n const obsidianOptions = getNormalizedObsidianOptions(browser.requestedCapabilities);\n\n if (obsidianOptions.emulateMobile && obsidianOptions.vault != undefined) {\n // I don't think this is technically necessary, but when you set the window size via emulateMobile it sets\n // the window size in the browser view, but not the actual window size which looks weird and makes it hard\n // to manually debug a paused test. The normal ways to set window size don't work on Obsidian. Obsidian\n // doesn't respect the `--window-size` argument, and wdio setViewport and setWindowSize don't work without\n // BiDi. This resizes the window directly using electron APIs.\n await browser.waitUntil(() => browser.execute(() => !!(window as any).electron));\n const [width, height] = await browser.execute(() => [window.innerWidth, window.innerHeight]);\n await browser.execute(async (width, height) => {\n await (window as any).electron.remote.getCurrentWindow().setSize(width, height);\n }, width, height);\n }\n\n // wait until app is loaded\n if (obsidianOptions.vault) {\n await browser.waitUntil( // wait until the helper plugin is loaded\n () => browser.execute(() => !!(window as any).wdioObsidianService),\n {timeout: 30 * 1000, interval: 100},\n );\n await browser.executeObsidian(async ({app}) => {\n await new Promise<void>((resolve) => app.workspace.onLayoutReady(resolve));\n });\n } else {\n await browser.execute(async () => {\n if (document.readyState === \"loading\") {\n return new Promise<void>(resolve => document.addEventListener(\"DOMContentLoaded\", () => resolve()));\n }\n });\n }\n }\n\n private createReloadObsidian() {\n const service = this; // eslint-disable-line @typescript-eslint/no-this-alias\n const reloadObsidian: WebdriverIO.Browser['reloadObsidian'] = async function(\n this: WebdriverIO.Browser,\n {vault, plugins, theme} = {},\n ) {\n const oldObsidianOptions = getNormalizedObsidianOptions(this.requestedCapabilities);\n const selectedPlugins = selectPlugins(oldObsidianOptions.plugins, plugins);\n const selectedThemes = selectThemes(oldObsidianOptions.themes, theme);\n if (!vault && oldObsidianOptions.vaultCopy == undefined) {\n throw Error(`No vault is open, pass a vault path to reloadObsidian`);\n }\n const newObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...oldObsidianOptions,\n // Resolve relative to PWD instead of root dir during tests\n vault: vault ? path.resolve(vault) : oldObsidianOptions.vault,\n plugins: selectedPlugins, themes: selectedThemes,\n };\n\n if (isAppium(this.requestedCapabilities)) {\n this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] = newObsidianOptions;\n if (vault) {\n await service.setupVault(this.requestedCapabilities);\n await service.appiumOpenVault();\n } else {\n // reload without resetting app state or triggering appium:fullReset\n\n // hack to disable the Obsidian app and make sure it doesn't write to the vault while we modify it\n await this.execute(() => {\n window.location.replace('http://localhost/_capacitor_file_/not-a-file');\n });\n\n // while Obsidian is down, modify the vault files to setup plugins and themes\n const local = path.join(oldObsidianOptions.vaultCopy!, \".obsidian\");\n const localCommunityPlugins = path.join(local, \"community-plugins.json\");\n const localAppearance = path.join(local, \"appearance.json\");\n const remote = `${oldObsidianOptions.uploadedVault!}/.obsidian`;\n const remoteCommunityPlugins = `${remote}/community-plugins.json`;\n const remoteAppearance = `${remote}/appearance.json`;\n\n await appiumDownloadFile(this, remoteCommunityPlugins, localCommunityPlugins).catch(() => {});\n await appiumDownloadFile(this, remoteAppearance, localAppearance).catch(() => {});\n await service.obsidianLauncher.setupVault({\n vault: oldObsidianOptions.vaultCopy!, copy: false,\n plugins: selectedPlugins, themes: selectedThemes,\n });\n let files = [localCommunityPlugins, localAppearance];\n files = (await Promise.all(files.map(async f => await fileExists(f) ? f : \"\"))).filter(f => f);\n await appiumUploadFiles(this, {src: local, dest: remote, files});\n\n // switch the app back\n await this.execute(() => {\n window.location.replace('http://localhost/');\n })\n }\n } else {\n // if browserName is set, reloadSession tries to restart the driver entirely, so unset those\n const newCap: WebdriverIO.Capabilities = _.cloneDeep(\n _.omit(this.requestedCapabilities, ['browserName', 'browserVersion'])\n );\n newCap[OBSIDIAN_CAPABILITY_KEY] = newObsidianOptions;\n\n if (vault) {\n await service.setupVault(newCap);\n await service.electronSetupConfigDir(newCap);\n await this.reloadSession(newCap);\n } else {\n // reload preserving current vault and config dir\n\n // Obsidian debounces saves to the config dir, and so changes to configuration made in the tests may\n // not get saved to disk before the reboot. I haven't found a better way to flush everything than\n // just waiting a bit.\n await this.pause(2000);\n\n await this.deleteSession({shutdownDriver: false});\n // while Obsidian is down, modify the vault files to setup plugins and themes\n await service.obsidianLauncher.setupVault({\n vault: oldObsidianOptions.vaultCopy!, copy: false,\n plugins: selectedPlugins, themes: selectedThemes,\n });\n await this.reloadSession(newCap);\n }\n }\n await service.prepareApp();\n };\n return reloadObsidian;\n }\n\n /**\n * Runs before the session and browser have started.\n */\n async beforeSession(config: Options.Testrunner, cap: WebdriverIO.Capabilities) {\n try {\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n if (cap[OBSIDIAN_CAPABILITY_KEY].vault != undefined) {\n await this.setupVault(cap);\n }\n if (!isAppium(cap)) {\n await this.electronSetupConfigDir(cap);\n }\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n\n /**\n * Runs after session and browser have started, but before tests.\n */\n async before(cap: WebdriverIO.Capabilities, specs: unknown, browser: WebdriverIO.Browser) {\n this.browser = browser;\n try {\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n\n // You are supposed to add commands via the addCommand hook, however you can't add synchronous methods that\n // way. Also, addCommand completely breaks the stack traces of errors from the methods, while tacking on the\n // methods manually doesn't.\n const newBrowserCommands = {\n ...browserCommands,\n reloadObsidian: this.createReloadObsidian(),\n }\n for (const [name, cmd] of Object.entries(newBrowserCommands)) {\n (browser as any)[name] = cmd;\n }\n\n if (isAppium(browser.requestedCapabilities)) {\n await this.appiumSetupApp();\n if (cap[OBSIDIAN_CAPABILITY_KEY].vault) {\n await this.appiumOpenVault();\n }\n }\n await this.prepareApp();\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n\n /** Runs after tests are done, but before the session is shut down */\n async after(result: number, cap: WebdriverIO.Capabilities) {\n const browser = this.browser!;\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n\n if (isAppium(cap)) {\n await this.appiumCloseVault();\n // \"mobile: deleteFile\" doesn't work on folders. \"mobile:shell\" requires appium --allow-insecure adb_shell\n await browser.execute(\"mobile: shell\", {command: \"rm\", args: [\"-rf\", this.androidVaultDir]});\n }\n }\n\n /**\n * Cleanup\n */\n async afterSession() {\n for (const tmpDir of this.tmpDirs) {\n await fsAsync.rm(tmpDir, { recursive: true, force: true });\n }\n }\n}\n","import * as path from \"path\"\nimport * as fsAsync from \"fs/promises\"\nimport * as crypto from \"crypto\";\nimport { fileURLToPath } from \"url\";\nimport { TFile } from \"obsidian\";\nimport _ from \"lodash\";\nimport { OBSIDIAN_CAPABILITY_KEY, NormalizedObsidianCapabilityOptions } from \"../types.js\";\nimport { BasePage } from \"./basePage.js\";\nimport { isAppium, normalizePath, isHidden, isText } from \"../utils.js\";\nimport { AppInternal } from \"../obsidianTypes.js\";\n\n\n/**\n * Class with various helper methods for writing Obsidian tests using the\n * [page object pattern](https://webdriver.io/docs/pageobjects).\n * \n * You can get an instance of this class either by running\n * ```ts\n * const obsidianPage = await browser.getObsidianPage();\n * ```\n * or just importing it directly with\n * ```ts\n * import { obsidianPage } from \"wdio-obsidian-service\";\n * ```\n * \n * @hideconstructor\n * @category Utilities\n */\nclass ObsidianPage extends BasePage {\n private getObsidianCapabilities(): NormalizedObsidianCapabilityOptions {\n return this.browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];\n }\n\n /**\n * Returns the path to the vault opened in Obsidian.\n * \n * wdio-obsidian-service copies your vault before running tests, so this is the path to the temporary copy.\n */\n getVaultPath(): string {\n const obsidianOptions = this.getObsidianCapabilities();\n if (obsidianOptions.vaultCopy === undefined) {\n throw Error(\"No vault open, set vault in wdio.conf.mts or use reloadObsidian to open a vault dynamically.\")\n }\n return obsidianOptions.uploadedVault ?? obsidianOptions.vaultCopy;\n }\n\n /**\n * Return the Obsidian config dir (just \".obsidian\" unless you changed the config dir name in settings).\n */\n async getConfigDir(): Promise<string> {\n return await this.browser.executeObsidian(({app}) => app.vault.configDir)\n }\n\n /**\n * Returns the Obsidian Platform object. Useful for skipping tests based on whether you are running in desktop or\n * mobile, or based on OS.\n */\n async getPlatform(): Promise<Platform> {\n const obsidianOptions = this.getObsidianCapabilities();\n if (obsidianOptions.vault !== undefined) {\n return await this.browser.executeObsidian(({obsidian}) => {\n const p = obsidian.Platform;\n return {\n isDesktop: p.isDesktop,\n isMobile: p.isMobile,\n isDesktopApp: p.isDesktopApp,\n isMobileApp: p.isMobileApp,\n isIosApp: p.isIosApp,\n isAndroidApp: p.isAndroidApp,\n isPhone: p.isPhone,\n isTablet: p.isTablet,\n isMacOS: p.isMacOS,\n isWin: p.isWin,\n isLinux: p.isLinux,\n isSafari: p.isSafari,\n };\n });\n } else {\n // hack to allow calling getPlatform before opening a vault. This is needed so you can use getPlatform to\n // skip a test before wasting time opening the vault. we don't use this method the rest of the time as you\n // can technically change the size or switch emulation mode during tests\n const appium = isAppium(this.browser.requestedCapabilities);\n const emulateMobile = obsidianOptions.emulateMobile;\n const [width, height] = await this.browser.execute(() => [window.innerWidth, window.innerHeight]);\n\n let isTablet = false;\n let isPhone = false;\n if (appium || emulateMobile) {\n // replicate Obsidian's tablet vs phone breakpoint\n isTablet = (width >= 600 && height >= 600);\n isPhone = !isTablet;\n }\n\n return {\n isDesktop: !(appium || emulateMobile),\n isMobile: appium || emulateMobile,\n isDesktopApp: !appium,\n isMobileApp: appium,\n isIosApp: false, // iOS is not supported\n isAndroidApp: appium,\n isPhone: isPhone,\n isTablet: isTablet,\n isMacOS: !appium && process.platform == 'darwin',\n isWin: !appium && process.platform == 'win32',\n isLinux: !appium && process.platform == 'linux',\n isSafari: false, // iOS is not supported\n };\n }\n }\n\n /**\n * Enables a plugin by ID\n */\n async enablePlugin(pluginId: string): Promise<void> {\n await this.browser.executeObsidian(\n async ({app}, pluginId) => await (app as AppInternal).plugins.enablePluginAndSave(pluginId),\n pluginId,\n );\n }\n\n /**\n * Disables a plugin by ID\n */\n async disablePlugin(pluginId: string): Promise<void> {\n await this.browser.executeObsidian(\n async ({app}, pluginId) => await (app as AppInternal).plugins.disablePluginAndSave(pluginId),\n pluginId,\n );\n }\n\n /**\n * Sets the theme. Pass \"default\" to reset to the Obsidian theme.\n */\n async setTheme(themeName: string): Promise<void> {\n themeName = themeName == 'default' ? '' : themeName;\n await this.browser.executeObsidian(\n async ({app}, themeName) => await (app as AppInternal).customCss.setTheme(themeName),\n themeName,\n )\n }\n\n /**\n * Opens a file in a new tab.\n */\n async openFile(path: string) {\n await this.browser.executeObsidian(async ({app, obsidian}, path) => {\n const file = app.vault.getAbstractFileByPath(path);\n if (file instanceof obsidian.TFile) {\n const leaf = app.workspace.getLeaf('tab');\n await leaf.openFile(file);\n app.workspace.setActiveLeaf(leaf, {focus: true});\n } else {\n throw Error(`No file ${path} exists`);\n }\n }, path)\n }\n\n /**\n * Loads a saved workspace layout from `.obsidian/workspaces.json` by name. Use the core \"Workspaces\"\n * plugin to create the layouts. You can also pass the layout object directly.\n */\n async loadWorkspaceLayout(layout: any): Promise<void> {\n if (typeof layout == \"string\") {\n // read from .obsidian/workspaces.json like the built-in workspaces plugin does\n const workspacesPath = `${await this.getConfigDir()}/workspaces.json`;\n const layoutName = layout;\n try {\n const fileContent = await this.browser.executeObsidian(async ({app}, workspacesPath) => {\n return await app.vault.adapter.read(workspacesPath);\n }, workspacesPath);\n layout = JSON.parse(fileContent)?.workspaces?.[layoutName];\n } catch {\n throw new Error(`Failed to load ${workspacesPath}:${layoutName}`);\n }\n if (!layout) {\n throw new Error(`No workspace ${layoutName} found in ${workspacesPath}`);\n }\n }\n\n await this.browser.executeObsidian(async ({app}, layout) => {\n await app.workspace.changeLayout(layout)\n }, layout)\n }\n\n /**\n * Deletes a file or folder in the vault.\n * @param file path of inside vault, e.g. \"/books/leviathan-wakes.md\"\n */\n async delete(file: string) {\n await this.browser.executeObsidian(async ({app, obsidian}, file, isVaultFile) => {\n file = obsidian.normalizePath(file);\n if (isVaultFile) {\n const fileObj = app.vault.getAbstractFileByPath(file);\n if (fileObj) {\n await app.vault.delete(fileObj, true);\n };\n } else {\n const stat = await app.vault.adapter.stat(file);\n if (stat && stat.type == \"folder\") {\n await app.vault.adapter.rmdir(file, true);\n } else if (stat) {\n await app.vault.adapter.remove(file);\n }\n }\n }, file, !isHidden(file));\n }\n\n /**\n * Writes to a file in the vault. Creates parent directories if needed.\n * @param file path of inside vault, e.g. \"/books/leviathan-wakes.md\"\n * @param content content to write to the file\n */\n async write(file: string, content: string|ArrayBuffer) {\n let strContent: string|undefined, binContent: string|undefined;\n if (typeof content == \"string\") {\n strContent = content;\n } else {\n binContent = Buffer.from(content).toString(\"base64\");\n }\n await this.browser.executeObsidian(async ({app, obsidian}, file, isVaultFile, strContent, binContent) => {\n file = obsidian.normalizePath(file);\n const parent = file.split(\"/\").slice(0, -1).join(\"/\");\n // there's a bug in Obsidian Android where occasionally creating a file silently fails, leaving just an\n // empty file. So retry if that happens. See https://forum.obsidian.md/t/102935\n let success = false;\n let retries = 0;\n while (!success && retries < 8) {\n if (isVaultFile) {\n if (parent && !app.vault.getAbstractFileByPath(parent)) {\n await app.vault.createFolder(parent);\n }\n const fileObj = app.vault.getAbstractFileByPath(file);\n strContent = strContent ?? atob(binContent!);\n if (fileObj) {\n await app.vault.modify(fileObj as TFile, strContent);\n } else {\n await app.vault.create(file, strContent);\n }\n } else {\n await app.vault.adapter.mkdir(parent);\n if (strContent) {\n await app.vault.adapter.write(file, strContent);\n } else {\n const buffer = Uint8Array.from(atob(binContent!), c => c.charCodeAt(0)).buffer;\n await app.vault.adapter.writeBinary(file, buffer);\n }\n }\n\n success = (\n !obsidian.Platform.isAndroidApp ||\n (strContent?.length ?? binContent!.length) === 0 ||\n (await app.vault.adapter.stat(file))!.size > 0\n );\n retries++;\n }\n if (!success) {\n throw new Error(`Failed to write file ${file}`);\n }\n }, file, !isHidden(file) && isText(file), strContent, binContent);\n }\n\n /**\n * Create a folder in the vault. Creates parent directories if needed.\n * @param file path of inside vault, e.g. \"/books\"\n */\n async mkdir(file: string) {\n await this.browser.executeObsidian(async ({app, obsidian}, file, isVaultFile) => {\n file = obsidian.normalizePath(file);\n if (isVaultFile) {\n if (parent && !app.vault.getAbstractFileByPath(file)) {\n await app.vault.createFolder(file);\n }\n } else {\n await app.vault.adapter.mkdir(file);\n }\n }, file, !isHidden(file));\n }\n\n /**\n * Updates the vault by modifying files in place without reloading Obsidian. Can be used to reset the vault back to\n * its original state or to \"switch\" to an entirely different vault without rebooting Obsidian\n * \n * This will only update regular vault files, it won't touch anything under `.obsidian`, and it won't reset any\n * Obsidian config or plugin settings. But if all you need is to reset the vault files, this can be used as a faster\n * alternative to {@link ObsidianBrowserCommands.reloadObsidian | reloadObsidian}.\n * \n * You'll often want to combine resetVault with something like this to reset your plugin's configuration as well:\n * ```ts\n * await browser.executeObsidian(async ({plugins}, settings) => {\n * Object.assign(plugins.myPlugin.settings, settings);\n * await plugins.myPlugin.saveSettings();\n * }, {...});\n * ```\n *\n * If no vault is passed, it resets the vault back to the oringal vault opened by the tests. You can also pass a\n * path to a different vault, and it will replace the current files with the files of that vault (similar to an\n * \"rsync\"). Or, instead of passing a vault path you can pass an object mapping vault file paths to file content.\n * E.g.\n * ```ts\n * obsidianPage.resetVault({\n * 'path/in/vault.md': \"Hello World\",\n * })\n * ```\n * \n * You can also pass multiple vaults and objects, and they will be merged. This can be useful if you want to add a\n * few small modifications to the base vault. e.g:\n * ```ts\n * obsidianPage.resetVault('./path/to/vault', {\n * \"books/leviathan-wakes.md\": \"...\",\n * })\n * ```\n */\n async resetVault(...vaults: (string|Record<string, string|ArrayBuffer>)[]) {\n const obsidianOptions = this.getObsidianCapabilities();\n if (!obsidianOptions.vault) {\n // open an empty vault if there's no vault open\n const defaultVaultPath = path.resolve(fileURLToPath(import.meta.url), '../../default-vault');\n await this.browser.reloadObsidian({vault: defaultVaultPath});\n }\n const configDir = await this.getConfigDir();\n vaults = vaults.length == 0 ? [obsidianOptions.vault!] : vaults;\n\n // list all files in the new vault\n type NewFileInfo = {type: \"file\"|\"folder\", sourceContent?: string|ArrayBuffer, sourceFile?: string};\n const newVault: Map<string, NewFileInfo> = new Map();\n for (let vault of vaults) {\n if (typeof vault == \"string\") {\n vault = path.resolve(vault);\n const files = await fsAsync.readdir(vault, { recursive: true, withFileTypes: true });\n for (const f of files) {\n const fullPath = path.join(f.parentPath, f.name);\n const vaultPath = path.relative(vault, fullPath).split(path.sep).join(\"/\");\n if (!vaultPath.startsWith(configDir + \"/\")) {\n if (f.isDirectory()) {\n newVault.set(vaultPath, {type: 'folder'});\n } else {\n newVault.set(vaultPath, {type: 'file', sourceFile: fullPath});\n }\n }\n }\n } else {\n vault = _.mapKeys(vault, (v, k) => normalizePath(k));\n for (const [file, content] of Object.entries(vault)) {\n newVault.set(file, {type: \"file\", sourceContent: content});\n }\n const folders = new Set(Object.keys(vault).map(p => path.posix.dirname(p)).filter(p => p !== \".\"));\n for (const folder of folders) {\n newVault.set(folder, {type: \"folder\"});\n }\n }\n }\n\n // list all files in the current vault\n type CurrFileInfo = {type: \"file\"|\"folder\", hash?: string};\n const currVault = new Map(await this.browser.executeObsidian(({app}, configDir) => {\n async function hash(data: ArrayBuffer) {\n const hashBuffer = await window.crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n }\n\n async function listRecursive(path: string): Promise<[string, CurrFileInfo][]> {\n const result: [string, CurrFileInfo][] = [];\n const { folders, files } = await app.vault.adapter.list(path);\n for (const folder of folders) {\n if (!folder.startsWith(configDir + \"/\")) {\n result.push([folder, {type: \"folder\"}]);\n result.push(...await listRecursive(folder));\n }\n }\n for (const file of files) {\n if (!file.startsWith(configDir + \"/\")) {\n let fileHash = (app as AppInternal).metadataCache.getFileInfo(file)?.hash;\n if (!fileHash) { // hidden files etc. aren't in Obsidian's metadata cache\n fileHash = await hash(await app.vault.adapter.readBinary(file));\n }\n result.push([file, { type: \"file\", hash: fileHash }]);\n }\n }\n return result;\n }\n\n return listRecursive(\"/\");\n }, configDir));\n\n // delete any files that need to be deleted\n for (const file of [...currVault.keys()].sort().reverse()) {\n const currFileInfo = currVault.get(file)!\n if (!newVault.has(file) || newVault.get(file)!.type != currFileInfo.type) {\n await this.delete(file);\n }\n }\n\n // create files and folders\n for (const [file, newFileInfo] of _.sortBy([...newVault.entries()], 0)) {\n const currFileInfo = currVault.get(file);\n if (newFileInfo.type == \"file\") {\n let content = newFileInfo.sourceContent;\n if (!content) {\n content = (await fsAsync.readFile(newFileInfo.sourceFile!)).buffer as ArrayBuffer;\n }\n const hash = crypto.createHash(\"SHA256\")\n .update(typeof content == \"string\" ? content : new Uint8Array(content))\n .digest(\"hex\");\n if (!currFileInfo || currFileInfo.hash != hash) {\n await this.write(file, content);\n }\n } else if (newFileInfo.type == \"folder\" && !currFileInfo) {\n await this.mkdir(file);\n }\n }\n }\n}\n\n/**\n * Info on the platform we are running on or emulating, in similar format as\n * [obsidian.Platform](https://docs.obsidian.md/Reference/TypeScript+API/Platform)\n * @category Types\n */\nexport interface Platform {\n /**\n * The UI is in desktop mode.\n */\n isDesktop: boolean;\n /**\n * The UI is in mobile mode.\n */\n isMobile: boolean;\n /**\n * We're running the Electron-based desktop app.\n * Note, when running under `emulateMobile` this will still be true and isDesktop will be false.\n */\n isDesktopApp: boolean;\n /**\n * We're running the Capacitor-js mobile app.\n * Note, when running under `emulateMobile` this will still be false and isMobile will be true.\n */\n isMobileApp: boolean;\n /** \n * We're running the iOS app.\n * Note, wdio-obsidian-service doesn't support iOS yet, so this will always be false.\n */\n isIosApp: boolean;\n /**\n * We're running the Android app.\n */\n isAndroidApp: boolean;\n /**\n * We're in a mobile app that has very limited screen space.\n */\n isPhone: boolean;\n /**\n * We're in a mobile app that has sufficiently large screen space.\n */\n isTablet: boolean;\n /**\n * We're on a macOS device, or a device that pretends to be one (like iPhones and iPads).\n * Typically used to detect whether to use command-based hotkeys vs ctrl-based hotkeys.\n */\n isMacOS: boolean;\n /**\n * We're on a Windows device.\n */\n isWin: boolean;\n /**\n * We're on a Linux device.\n */\n isLinux: boolean;\n /**\n * We're running in Safari.\n * Note, wdio-obsidian-service doesn't support iOS yet, so this will always be false.\n */\n isSafari: boolean;\n};\n\n/**\n * Instance of {@link ObsidianPage} with helper methods for writing Obsidian tests.\n * @category Utilities\n */\nconst obsidianPage = new ObsidianPage()\nexport default obsidianPage;\nexport { ObsidianPage };\n","import type { ObsidianBrowserCommands } from \"./browserCommands.js\";\nimport type { PluginEntry, ThemeEntry, DownloadedPluginEntry, DownloadedThemeEntry } from \"obsidian-launcher\";\n\nexport const OBSIDIAN_CAPABILITY_KEY = \"wdio:obsidianOptions\";\n\n/**\n * Options passed to an \"wdio:obsidianOptions\" capability in wdio.conf.mts. E.g.\n * ```ts\n * // ...\n * capabilities: [{\n * browserName: \"obsidian\",\n * browserVersion: \"latest\",\n * 'wdio:obsidianOptions': {\n * installerVersion: 'earliest',\n * plugins: [\".\"],\n * },\n * }],\n * ```\n * \n * @category Options\n */\nexport interface ObsidianCapabilityOptions {\n /**\n * Version of Obsidian to download and run.\n * \n * Can be set to a specific version or one of:\n * - \"latest\": Run the latest non-beta Obsidian version\n * - \"latest-beta\": Run the latest beta Obsidian version (or latest if there is no current beta)\n * - To download Obsidian beta versions you'll need to have an Obsidian Insiders account and either set the \n * `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` env vars (`.env` file is supported) or pre-download the Obsidian \n * beta version with `npx obsidian-launcher download app -v latest-beta`\n * - \"earliest\": Run the `minAppVersion` set in your `manifest.json`\n * \n * Defaults to \"latest\".\n * \n * You can also use the wdio capability `browserVersion` field to set the Obsidian version.\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n */\n appVersion?: string\n\n /**\n * Version of the Obsidian installer to download and run.\n * \n * Obsidian is Desktop distributed in two parts, the app which contains the JS, and the installer which is the\n * binary with Electron. Obsidian's auto update only updates the app, so users on the same Obsidian version can be\n * running different Electron versions. You can use this to test your plugin against different installer/Electron\n * versions.\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n *\n * Can be set to a specific version string or one of:\n * - \"latest\": Run the latest Obsidian installer compatible with `appVersion`.\n * - \"earliest\": Run the oldest Obsidian installer compatible with `appVersion`.\n * \n * Defaults to \"earliest\".\n */\n installerVersion?: string,\n\n /**\n * List of plugins to install.\n * \n * Each entry is a path to the local plugin to install, e.g. [\".\"] or [\"dist\"] depending on your build setup. Paths\n * are relative to your `wdio.conf.mts`. You can also pass objects. If you pass an object it should contain one of\n * `path` (to install a local plugin), `repo` (to install a plugin from GitHub), or `id` (to install a community\n * plugin). You can set `enabled: false` to install the plugin but start it disabled. You can enable the plugin\n * later using {@link ObsidianBrowserCommands.reloadObsidian|browser.reloadObsidian} or\n * {@link ObsidianPage.enablePlugin}.\n */\n plugins?: PluginEntry[],\n\n /**\n * List of themes to install.\n * \n * Each entry is a path to the local theme to install. Paths are relative to your `wdio.conf.mts`. You can also pass\n * an object. If you pass an object it should contain one of `path` (to install a local theme), `repo` (to install a\n * theme from GitHub), or `name` (to install a community theme). You can set `enabled: false` to install the theme,\n * but start it disabled. You can only have one enabled theme, so if you pass multiple you'll have to disable all\n * but one.\n */\n themes?: ThemeEntry[],\n\n /**\n * The path to the vault to open.\n * \n * The vault will be copied, so any changes made in your tests won't affect the original. If omitted, no vault will\n * be opened and you'll need to call {@link ObsidianBrowserCommands.reloadObsidian|browser.reloadObsidian} to open a\n * vault during your tests. Path is relative to your `wdio.conf.mts`.\n */\n vault?: string,\n\n /**\n * Set to true to emulate mobile on the Electron desktop app. This uses Obsidian `app.emulateMobile()` to switch\n * Obsidian to the mobile UI, and you can use Chrome's mobileEmulation to set the screen size. You can compare\n * tablet vs phone UIs by setting the screen size or emulated device. Obsidian tablet UI triggers at\n * width/height >= 600.\n *\n * Note that Obsidian Mobile runs on Capacitor instead of Electron so there are various platform differences that\n * can't be emulated. But it's good enough for most cases as long as you aren't interacting directly with the\n * operating system or Electron APIs. You can use an Android Virtual Device instead if you want a more accurate\n * (but slower) mobile test.\n *\n * See [Mobile Emulation](../README.md#mobile-emulation) for more info.\n * See [Android](../README.md#android) if you want to test the real mobile app on an Android Virtual Device instead.\n */\n emulateMobile?: boolean,\n\n /**\n * Path to the Obsidian binary to use. If omitted it will be downloaded automatically.\n */\n binaryPath?: string,\n\n /**\n * Path to the app asar to load into obsidian. If omitted it will be downloaded automatically.\n */\n appPath?: string,\n}\n\n\n/** Internal type, capability options after being normalized by onPrepare */\nexport interface NormalizedObsidianCapabilityOptions {\n appVersion: string\n installerVersion: string,\n plugins: DownloadedPluginEntry[],\n themes: DownloadedThemeEntry[],\n vault?: string,\n vaultCopy?: string,\n /** Path of the vault on the appium device */\n uploadedVault?: string,\n emulateMobile: boolean,\n binaryPath?: string,\n appPath?: string,\n}\n\n\n/**\n * Options passed to wdio-obsidian-service service in wdio.conf.mts. E.g.\n * ```js\n * // ...\n * services: [[\"obsidian\", {versionsUrl: \"file:///path/to/obsidian-versions.json\"}]]\n * ```\n * You'll usually want to leave these options as the default, they are mostly useful for wdio-obsidian-service's\n * internal tests.\n * \n * @category Options\n */\nexport interface ObsidianServiceOptions {\n /**\n * Override the `obsidian-versions.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/jesse-r-s-hines/wdio-obsidian-service/HEAD/obsidian-versions.json\n * which is auto-updated to contain information on available Obsidian versions.\n */\n versionsUrl?: string,\n /**\n * Override the `community-plugins.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-plugins.json\n */\n communityPluginsUrl?: string,\n /**\n * Override the `community-css-themes.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-css-themes.json\n */\n communityThemesUrl?: string,\n}\n\ndeclare global {\n namespace WebdriverIO {\n interface Capabilities {\n [OBSIDIAN_CAPABILITY_KEY]?: ObsidianCapabilityOptions,\n }\n\n interface Browser extends ObsidianBrowserCommands {}\n }\n}\n","// src/index.ts\nvar globals = globalThis._wdioGlobals = globalThis._wdioGlobals || /* @__PURE__ */ new Map();\nvar GLOBALS_ERROR_MESSAGE = `No browser instance registered. Don't import @wdio/globals outside of the WDIO testrunner context. Or you have two two different \"@wdio/globals\" packages installed.`;\nfunction proxyHandler(key) {\n return {\n get: (self, prop) => {\n if (!globals.has(key)) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n const receiver = globals.get(key);\n const field = receiver[prop];\n return typeof field === \"function\" ? field.bind(receiver) : field;\n }\n };\n}\nvar browser = new Proxy(\n class Browser {\n },\n proxyHandler(\"browser\")\n);\nvar driver = new Proxy(\n class Browser2 {\n },\n proxyHandler(\"driver\")\n);\nvar multiremotebrowser = new Proxy(\n class Browser3 {\n },\n proxyHandler(\"multiremotebrowser\")\n);\nvar $ = (...args) => {\n if (!globals.has(\"$\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"$\")(...args);\n};\nvar $$ = (...args) => {\n if (!globals.has(\"$$\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"$$\")(...args);\n};\nvar expect = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\")(...args);\n};\nvar ASYNC_MATCHERS = [\n \"any\",\n \"anything\",\n \"arrayContaining\",\n \"objectContaining\",\n \"stringContaining\",\n \"stringMatching\"\n];\nfor (const matcher of ASYNC_MATCHERS) {\n expect[matcher] = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\")[matcher](...args);\n };\n}\nexpect.not = ASYNC_MATCHERS.reduce((acc, matcher) => {\n acc[matcher] = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\").not[matcher](...args);\n };\n return acc;\n}, {});\nexpect.extend = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n const expect2 = globals.get(\"expect\");\n return expect2.extend(...args);\n};\nfunction _setGlobal(key, value, setGlobal = true) {\n globals.set(key, value);\n if (setGlobal) {\n globalThis[key] = value;\n }\n}\nexport {\n $,\n $$,\n _setGlobal,\n browser,\n driver,\n expect,\n multiremotebrowser\n};\n","import * as wdioGlobals from \"@wdio/globals\"\n\n/**\n * Base page object for use in the wdio [page object pattern](https://webdriver.io/docs/pageobjects).\n * \n * You can pass the browser to the page object, which allows using the object even in wdio standalone mode.\n */\nexport class BasePage {\n private _browser: WebdriverIO.Browser|undefined\n constructor(browser?: WebdriverIO.Browser) {\n this._browser = browser;\n }\n\n /**\n * Returns the browser instance.\n * @hidden\n */\n protected get browser(): WebdriverIO.Browser {\n return this._browser ?? wdioGlobals.browser;\n }\n}\n","import path from \"path\";\nimport crypto from \"crypto\";\nimport fsAsync from \"fs/promises\";\nimport * as tar from \"tar\";\nimport _ from \"lodash\";\n\n/** Quote string for use in shell scripts */\nexport function quote(input: string) {\n return `'${input.replace(/'/g, \"'\\\\''\")}'`;\n}\n\nexport async function fileExists(path: string) {\n try {\n await fsAsync.access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Gets the appium options.\n * Handles combining `\"appium:foo\"` and `\"appium:options\": {\"foo\": ...}` style options.\n */\nexport function getAppiumOptions(\n cap: WebdriverIO.Capabilities,\n): Exclude<WebdriverIO.Capabilities['appium:options'], undefined> {\n let result = _.pickBy(cap, (v, k) => k.startsWith(\"appium:\") && k != 'appium:options');\n result = _.mapKeys(result, (v, k) => k.slice(7))\n return {...result, ...cap['appium:options']};\n}\n\n/** Returns true if this capability is for Appium */\nexport function isAppium(cap: WebdriverIO.Capabilities) {\n return getAppiumOptions(cap).automationName?.toLocaleLowerCase() === 'uiautomator2';\n}\n\n/**\n * Upload multiple files at once. Uploads files as a tar for much better performance and to avoid issues with special\n * characters in names. (see https://github.com/appium/appium-android-driver/issues/1004)\n * \n * @param opts.src Directory to copy from (-C in tar)\n * @param opts.dest Directory to copy to\n * @param opts.files Files under src to copy. Defaults to the whole dir.\n * @param opts.chunkSize Size to split the tar into while uploading (to avoid excessive RAM usage)\n */\nexport async function appiumUploadFiles(browser: WebdriverIO.Browser, opts: {\n src: string, dest: string, files?: string[], chunkSize?: number,\n}) {\n let {\n src, dest,\n files = [src],\n chunkSize = 2 * 1024 * 1024,\n } = opts;\n src = path.resolve(src);\n dest = path.posix.normalize(dest).replace(/\\/$/, '');\n files = files.map(f => path.relative(src, f) || \".\");\n if (files.length == 0) return; // nothing to do\n\n // We'll tar the files up and then extract the tar on Android side. This is a lot faster than doing many small\n // pushFiles. Since pushFile requires sending the whole file contents as a base64 string, we'll split up the tar\n // into chunks to keep the size manageable for large vaults.\n\n const tmpDir = \"/data/local/tmp\"\n const slug = crypto.randomBytes(10).toString(\"base64url\").replace(/[-_]/g, '0');\n const stream = tar.create({C: src, gzip: { level: 2 }}, files);\n\n // buffer/chunk size of the tar stream varies. Without compression it appears to be one chunk per file, splitting\n // large files at maxReadSize, plus some chunks for the header. However compression adds another layer that appears\n // to limit the output chunk size to about 16K.\n let buffers: Buffer[] = [];\n let bufferSize = 0;\n let i = 0;\n for await (const chunk of stream) {\n if (bufferSize + chunk.length > chunkSize) {\n const data = Buffer.concat(buffers).toString('base64');\n await browser.pushFile(`${tmpDir}/${slug}-${String(i).padStart(6, '0')}.tar`, data);\n i++;\n buffers = [];\n bufferSize = 0;\n }\n buffers.push(chunk);\n bufferSize += chunk.length;\n }\n const data = Buffer.concat(buffers).toString('base64');\n await browser.pushFile(`${tmpDir}/${slug}-${String(i).padStart(6, '0')}.tar`, data);\n\n // extract the tar. `mobile: shell` does NOT escape arguments despite taking an argument array.\n await browser.execute(\"mobile: shell\", {command: \"sh\", args: [\"-c\", quote(`\n mkdir -p ${quote(dest)};\n cat ${tmpDir}/${slug}-*.tar | tar -xz -C ${quote(dest)};\n rm ${tmpDir}/${slug}-*.tar;\n `)]});\n}\n\nexport async function appiumDownloadFile(browser: WebdriverIO.Browser, src: string, dest: string) {\n src = path.posix.normalize(src);\n dest = path.resolve(dest);\n const content = Buffer.from(await browser.pullFile(src), \"base64\");\n await fsAsync.writeFile(dest, content);\n}\n\n/** Lists all files under a folder. Returns full paths. */\nexport async function appiumReaddir(browser: WebdriverIO.Browser, dir: string) {\n // list all files, one per line, no escaping\n const stdout: string = await browser.execute(\"mobile: shell\", {command: \"ls\", args: [\"-NA1\", dir]});\n return stdout.split(\"\\n\").filter(f => f).map(f => path.join(dir, f)).sort();\n}\n\n/** SHA256 hash */\nexport async function hash(content: string|ArrayBufferLike) {\n return crypto.createHash(\"SHA256\")\n .update(typeof content == \"string\" ? content : new Uint8Array(content))\n .digest(\"hex\");\n}\n\n/** Replicate obsidian.normalizePath */\nexport function normalizePath(p: string) {\n p = p.replace(/([\\\\/])+/g, \"/\").replace(/(^\\/+|\\/+$)/g, \"\");\n if (p == \"\") {\n p = \"/\"\n }\n p = p.replace(/\\u00A0|\\u202F/g, \" \"); // replace special space characters\n p = p.normalize(\"NFC\");\n return p;\n}\n\n/** Returns true if a vault file path is hidden (either it or one of it's parent directories starts with \".\") */\nexport function isHidden(file: string) {\n return file.split(\"/\").some(p => p.startsWith(\".\"))\n}\n\n/** Returns true if this is a simple text file */\nexport function isText(file: string) {\n return [\".md\", \".json\", \".txt\", \".js\"].includes(path.extname(file).toLocaleLowerCase());\n}\n","import type * as obsidian from \"obsidian\"\nimport { ObsidianPage } from \"./pageobjects/obsidianPage.js\"\nimport { NormalizedObsidianCapabilityOptions, OBSIDIAN_CAPABILITY_KEY } from \"./types.js\";\nimport { AppInternal } from \"./obsidianTypes.js\";\n\nexport const browserCommands = {\n /**\n * Wrapper around browser.execute that passes the Obsidian API to the function. The first argument to the function\n * is an object containing keys:\n * - app: Obsidian app instance\n * - obsidian: Full Obsidian API\n * - plugins: Object of all installed plugins, mapped by plugin id converted to camelCase.\n * - require: The customized require function Obsidian makes available to plugins. This is also available globally,\n * so you can just use `require` directly instead of from {@link ExecuteObsidianArg} if you prefer.\n * \n * Like `brower.execute`, you can pass other extra arguments to the function.\n * \n * See also: https://webdriver.io/docs/api/browser/execute\n * \n * Example usage\n * ```ts\n * const file = browser.executeObsidian(({app, obsidian}, path) => {\n * return app.vault.getMarkdownFiles().find(f => f.path == path);\n * })\n * ```\n * \n * Note: The same caveats as `browser.execute` apply. The function will be stringified and then run inside Obsidian,\n * so you can't capture any local variables. E.g.\n * \n * This *won't* work:\n * ```ts\n * import { FileView } from \"obsidian\"\n * browser.executeObsidian(({app}) => {\n * if (leaf.view instanceof FileView) {\n * ...\n * }\n * })\n * ```\n * do this instead:\n * ```ts\n * browser.executeObsidian(({app, obsidian}) => {\n * if (leaf.view instanceof obsidian.FileView) {\n * ...\n * }\n * })\n * ```\n */\n async executeObsidian<Return, Params extends unknown[]>(\n this: WebdriverIO.Browser,\n func: (obs: ExecuteObsidianArg, ...params: Params) => Return,\n ...params: Params\n ): Promise<Return> {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n if (obsidianOptions.vault === undefined) {\n throw Error(\"No vault open, set vault in wdio.conf.mts or use reloadObsidian to open a vault dynamically.\")\n }\n // Call the function. Just stringify the function (browser.execute stringifies it anyways). Add a workaround for\n // node fs errors returning useless error messages. If the thrown error has a \"code\" field that is a string, the\n // chromedriver throws an error like this with no further info:\n // \"unknown error: call function result missing int 'status'\"\n // I think this is a bug in ChromeDevtoolsProtocol or chromedriver. See\n // https://github.com/electron-userland/spectron/issues/1057\n const result = await this.execute<Return, Params>(`\n const require = window.wdioObsidianService().require;\n try {\n return await (\n ${func.toString()}\n ).call(null, window.wdioObsidianService(), ...arguments);\n } catch (e) {\n if (\"code\" in e && typeof e.code != \"number\") {\n delete e.code;\n }\n throw e;\n }\n `, ...params);\n // TODO Should maybe add in the TransformReturn and TransformElement bit that wdio has recently added to the\n // execute types, though it causes weird affects if `func` returns type any.\n return result as Return;\n },\n\n /**\n * Executes an Obsidian command by id.\n * @param id Id of the command to run.\n */\n async executeObsidianCommand(this: WebdriverIO.Browser, id: string) {\n const result = await this.executeObsidian(\n ({app}, id) => (app as AppInternal).commands.executeCommandById(id),\n id,\n );\n if (!result) {\n throw Error(`Obsidian command ${id} not found or failed.`);\n }\n },\n\n /**\n * Returns the Obsidian app version this test is running under.\n */\n getObsidianVersion(this: WebdriverIO.Browser): string {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n return obsidianOptions.appVersion;\n },\n \n /**\n * Returns the Obsidian installer version this test is running under.\n */\n getObsidianInstallerVersion(this: WebdriverIO.Browser): string {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n return obsidianOptions.installerVersion;\n },\n\n /**\n * Returns the ObsidianPage object with convenience helper functions.\n * You can also just import the page object directly with\n * ```ts\n * import { obsidianPage } from \"wdio-obsidian-service\"\n * ```\n */\n getObsidianPage(this: WebdriverIO.Browser): ObsidianPage {\n return new ObsidianPage(this);\n },\n}\n\n/**\n * Extra commands added to the WDIO Browser instance.\n * \n * See also: https://webdriver.io/docs/api/browser\n * @interface\n * @category Utilities\n */\nexport type ObsidianBrowserCommands = typeof browserCommands & {\n /**\n * Relaunch obsidian. Can be used to switch to a new vault, change the plugin list, or just to reboot Obsidian.\n * \n * As this does a full reboot of Obsidian, this is rather slow. In many cases you can use\n * {@link ObsidianPage.resetVault} instead, which modifies vault files in place without rebooting Obsidian. If all\n * your tests use the same vault, you can also just set the vault in the `wdio.conf.mts` capabilities section.\n * \n * @param params.vault Path to the vault to open. The vault will be copied, so any changes made in your tests won't\n * be persited to the original. If omitted, it will reboot Obsidian with the current vault without creating a\n * new copy of the vault.\n * @param params.plugins List of plugin ids to enable. If omitted it will keep current plugin list. Note, all the\n * plugins must be defined in your wdio.conf.mts capabilities. You can also use the enablePlugin and \n * disablePlugin commands to change plugins without relaunching Obsidian.\n * @param params.theme Name of the theme to enable. If omitted it will keep the current theme. Pass \"default\" to\n * switch back to the default theme. Like with plugins, the theme must be defined in wdio.conf.mts.\n */\n reloadObsidian(params?: {\n vault?: string,\n plugins?: string[], theme?: string,\n }): Promise<void>;\n // This command is implemented in the service hooks.\n};\n\n/**\n * Argument passed to the {@link ObsidianBrowserCommands.executeObsidian | executeObsidian} browser command.\n * @category Types\n */\nexport interface ExecuteObsidianArg {\n /**\n * There is a global \"app\" instance, but that may be removed in the future so you can use this to access it from\n * tests. See https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines#Avoid+using+global+app+instance\n */\n app: obsidian.App,\n\n /**\n * The full obsidian API. See https://github.com/obsidianmd/obsidian-api/blob/master/obsidian.d.ts\n */\n obsidian: typeof obsidian,\n\n /**\n * Object containing all installed plugins mapped by their id. Plugin ids are converted to camelCase for ease of\n * destructuring.\n * \n * If you want to add typings for your plugin(s) you can use something like this in a `.d.ts`:\n * ```ts\n * import type MyPlugin from \"../src/main.js\"\n * declare module \"wdio-obsidian-service\" {\n * interface InstalledPlugins {\n * myPlugin: MyPlugin,\n * }\n * }\n * ```\n */\n plugins: InstalledPlugins,\n\n /**\n * The customized require function Obsidian makes available to plugins. This is also available globally, so you can\n * just use `require` directly instead of from {@link ExecuteObsidianArg} if you prefer.\n */\n require: NodeJS.Require,\n}\n\n/**\n * Installed plugins, mapped by their id converted to camelCase\n * @category Types\n */\nexport interface InstalledPlugins extends Record<string, obsidian.Plugin> {\n}\n","import { remote } from 'webdriverio'\nimport type { Capabilities, Options } from '@wdio/types'\nimport { ObsidianLauncherService, ObsidianWorkerService } from \"./service.js\"\nimport { ObsidianServiceOptions } from \"./types.js\"\n\n/**\n * Starts an Obsidian instance for WDIO standalone mode.\n * \n * For testing, you'll usually want to use the WDIO testrunner with Mocha, wdio.conf.mts, etc. to launch WDIO. However\n * if you want to use WDIO for some kind of scripting scenario, you can use this function to launch a WDIO standalone\n * session connected to Obsidian.\n * \n * See also: https://webdriver.io/docs/setuptypes/#standalone-mode\n * \n * @category WDIO Helpers\n */\nexport async function startWdioSession(\n params: Capabilities.WebdriverIOConfig,\n serviceOptions?: ObsidianServiceOptions,\n): Promise<WebdriverIO.Browser> {\n serviceOptions = serviceOptions ?? {};\n const capabilities = params.capabilities as WebdriverIO.Capabilities;\n const testRunnerOptions: Options.Testrunner = {\n cacheDir: params.cacheDir,\n };\n const launcherService = new ObsidianLauncherService(\n serviceOptions,\n [capabilities] as WebdriverIO.Capabilities,\n testRunnerOptions,\n );\n const workerService = new ObsidianWorkerService(serviceOptions, capabilities, testRunnerOptions);\n\n await launcherService.onPrepare(testRunnerOptions, [capabilities]);\n await workerService.beforeSession(testRunnerOptions, capabilities);\n\n const browser = await remote(params);\n\n await workerService.before(capabilities, [], browser);\n\n return browser;\n}\n"],"mappings":"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","names":["ObsidianLauncher","fsAsync","path","fileURLToPath","path","fsAsync","crypto","_","browser","path","browser","data","pluginId","themeName","path","workspacesPath","layout","file","strContent","binContent","parent","_","configDir","id","_","path","fileURLToPath","browser","androidVault","width","height","resolve","remote","fsAsync","browser","launcher","ObsidianLauncher"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/service.ts","../src/pageobjects/obsidianPage.ts","../src/types.ts","../../../node_modules/.pnpm/@wdio+globals@9.17.0_expect-webdriverio@5.4.1_webdriverio@9.18.4/node_modules/@wdio/globals/build/index.js","../src/pageobjects/basePage.ts","../src/utils.ts","../src/browserCommands.ts","../src/standalone.ts"],"sourcesContent":["/**\n * @module\n * @document ../README.md\n * @categoryDescription Options\n * Capability and service options.\n * @categoryDescription WDIO Helpers\n * Helpers for use in wdio.conf.mts, or for launching WDIO in standalone mode.\n * @categoryDescription Utilities\n * Browser commands and helper functions for writing tests.\n */\nimport ObsidianLauncher from \"obsidian-launcher\";\nimport { deprecate } from \"util\";\n\nimport { ObsidianLauncherService, ObsidianWorkerService } from \"./service.js\";\n/** @hidden */\nexport default ObsidianWorkerService;\n/** @hidden */\nexport const launcher = ObsidianLauncherService;\n\nexport type { ObsidianCapabilityOptions, ObsidianServiceOptions } from \"./types.js\";\nexport type { ObsidianBrowserCommands, ExecuteObsidianArg, InstalledPlugins } from \"./browserCommands.js\";\nexport { default as obsidianPage } from \"./pageobjects/obsidianPage.js\";\nexport type { ObsidianPage, Platform } from \"./pageobjects/obsidianPage.js\";\nexport type { PluginEntry, ThemeEntry } from \"obsidian-launcher\";\n\nexport { minSupportedObsidianVersion } from \"./service.js\";\nexport { startWdioSession } from \"./standalone.js\";\n\n// Some convenience helpers for use in wdio.conf.mts\n\n/**\n * Returns true if there is a current Obsidian beta and we have the credentials to download it, or its already in cache.\n * @category WDIO Helpers\n * @param opts.cacheDir Obsidian cache dir, defaults to `.obsidian-cache`.\n */\nexport async function obsidianBetaAvailable(opts: string|{cacheDir?: string} = {}) {\n opts = typeof opts == \"string\" ? {cacheDir: opts} : opts;\n const launcher = new ObsidianLauncher(opts);\n const versionInfo = await launcher.getVersionInfo(\"latest-beta\");\n return versionInfo.isBeta && await launcher.isAvailable(versionInfo.version);\n}\n\n/**\n * Resolves Obsidian app and installer version strings to absolute versions.\n * \n * @category WDIO Helpers\n * @deprecated Use parseObsidianVersions instead\n */\nexport const resolveObsidianVersions = deprecate(async function(\n appVersion: string, installerVersion: string, cacheDir?: string,\n): Promise<[string, string]> {\n const launcher = new ObsidianLauncher({cacheDir: cacheDir});\n return await launcher.resolveVersion(appVersion, installerVersion);\n}, 'resolveObsidianVersions is deprecated, use parseObsidianVersions instead');\n\n\n/**\n * Parses a string of Obsidian versions into [appVersion, installerVersion] tuples. This is a convenience helper for use\n * in `wdio.conf.mts`\n * \n * `versions` should be a space separated list of Obsidian app versions. You can optionally specify the installer\n * version by using \"appVersion/installerVersion\" e.g. `\"1.7.7/1.8.10\"`.\n * \n * Example: \n * ```js\n * parseObsidianVersions(\"1.8.10/1.7.7 latest latest-beta/earliest\")\n * ```\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n * \n * @category WDIO Helpers\n * @param versions string to parse\n * @param opts.cacheDir Obsidian cache dir, defaults to `.obsidian-cache`.\n * @returns [appVersion, installerVersion][] resolved to specific versions.\n */\nexport async function parseObsidianVersions(\n versions: string,\n opts: {cacheDir?: string} = {},\n): Promise<[string, string][]> {\n const launcher = new ObsidianLauncher({cacheDir: opts.cacheDir});\n return launcher.parseVersions(versions);\n}\n","import fs from \"fs\"\nimport fsAsync from \"fs/promises\"\nimport path from \"path\"\nimport { SevereServiceError } from 'webdriverio'\nimport type { Capabilities, Options, Services } from '@wdio/types'\nimport logger from '@wdio/logger'\nimport { fileURLToPath } from \"url\"\nimport ObsidianLauncher, {\n PluginEntry, ThemeEntry, DownloadedPluginEntry, DownloadedThemeEntry,\n} from \"obsidian-launcher\"\nimport { browserCommands } from \"./browserCommands.js\"\nimport {\n ObsidianServiceOptions, NormalizedObsidianCapabilityOptions, OBSIDIAN_CAPABILITY_KEY,\n} from \"./types.js\"\nimport {\n isAppium, appiumUploadFiles, appiumDownloadFile, getAppiumOptions, fileExists,\n} from \"./utils.js\";\nimport semver from \"semver\"\nimport _ from \"lodash\"\n\n\nconst log = logger(\"wdio-obsidian-service\");\n\nfunction getDefaultCacheDir(rootDir: string) {\n return path.resolve(rootDir, process.env.WEBDRIVER_CACHE_DIR ?? process.env.OBSIDIAN_CACHE ?? \"./.obsidian-cache\");\n}\n\n/** By default wdio continues on service errors, so we throw a SevereServiceError to make it bail on error */\nfunction getServiceErrorMessage(e: any) {\n return (\n `Failed to download and setup Obsidian. Caused by:\\n` +\n `${e.stack}\\n`+\n ` ------The above causes:-----`\n );\n}\n\nfunction resolveEntry(rootDir: string, entry: PluginEntry|ThemeEntry): PluginEntry|ThemeEntry {\n if (typeof entry == \"string\") {\n return path.resolve(rootDir, entry);\n } else if ('path' in entry) {\n return {...entry, path: path.resolve(rootDir, entry.path)};\n } else {\n return entry;\n }\n}\n\n/** Returns a plugin list with only the selected plugin ids enabled. */\nfunction selectPlugins(currentPlugins: DownloadedPluginEntry[], selection?: string[]): DownloadedPluginEntry[] {\n if (selection !== undefined) {\n const unknownPlugins = _.difference(selection, currentPlugins.map(p => p.id));\n if (unknownPlugins.length > 0) {\n throw Error(`Unknown plugin ids: ${unknownPlugins.join(', ')}`)\n }\n return currentPlugins.map(p => ({\n ...p,\n enabled: selection.includes(p.id) || p.id == \"wdio-obsidian-service-plugin\",\n }));\n } else {\n return currentPlugins;\n }\n}\n\n/** Returns a theme list with only the selected theme enabled. */\nfunction selectThemes(currentThemes: DownloadedThemeEntry[], selection?: string): DownloadedThemeEntry[] {\n if (selection !== undefined) {\n if (selection != \"default\" && currentThemes.every(t => t.name != selection)) {\n throw Error(`Unknown theme: ${selection}`);\n }\n return currentThemes.map(t => ({...t, enabled: selection != 'default' && t.name === selection}));\n } else {\n return currentThemes;\n }\n}\n\nfunction getNormalizedObsidianOptions(cap: WebdriverIO.Capabilities): NormalizedObsidianCapabilityOptions {\n return cap[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n}\n\n\n/**\n * Minimum Obsidian version that wdio-obsidian-service supports.\n * @category WDIO Helpers\n */\nexport const minSupportedObsidianVersion: string = \"1.0.3\"\n\n\n/**\n * wdio launcher service.\n * Use in wdio.conf.mts like so:\n * ```ts\n * services: ['obsidian'],\n * ```\n * @hidden\n */\nexport class ObsidianLauncherService implements Services.ServiceInstance {\n private obsidianLauncher: ObsidianLauncher\n private readonly helperPluginPath: string\n private readonly rootDir: string\n\n constructor (\n public options: ObsidianServiceOptions,\n public capabilities: WebdriverIO.Capabilities,\n public config: Options.Testrunner\n ) {\n this.rootDir = config.rootDir || process.cwd();\n this.obsidianLauncher = new ObsidianLauncher({\n cacheDir: config.cacheDir ?? getDefaultCacheDir(this.rootDir),\n versionsUrl: options.versionsUrl,\n communityPluginsUrl: options.communityPluginsUrl, communityThemesUrl: options.communityThemesUrl,\n });\n this.helperPluginPath = path.resolve(fileURLToPath(import.meta.url), '../../helper-plugin');\n }\n\n /**\n * Validates wdio:obsidianOptions and downloads Obsidian, plugins, and themes.\n */\n async onPrepare(config: Options.Testrunner, capabilities: Capabilities.TestrunnerCapabilities) {\n try {\n if (!Array.isArray(capabilities)) {\n capabilities = Object.values(capabilities).map(\n (multiremoteOption) => (multiremoteOption as Capabilities.WithRequestedCapabilities).capabilities,\n );\n }\n\n const obsidianCapabilities = capabilities.flatMap((cap) => {\n if ((\"browserName\" in cap) && cap.browserName === \"obsidian\") {\n return [cap as WebdriverIO.Capabilities];\n } else {\n return [];\n }\n });\n\n for (const cap of obsidianCapabilities) {\n const obsidianOptions = cap[OBSIDIAN_CAPABILITY_KEY] ?? {};\n \n // check vault\n const vault = obsidianOptions.vault ? path.resolve(this.rootDir, obsidianOptions.vault) : undefined;\n if (vault && !fs.existsSync(vault)) {\n throw Error(`Vault \"${vault}\" doesn't exist`)\n }\n \n // download plugins and themes to cache\n const plugins = await this.obsidianLauncher.downloadPlugins(\n (obsidianOptions.plugins ?? [])\n .concat([this.helperPluginPath]) // Always install the helper plugin\n .map(p => resolveEntry(this.rootDir, p) as PluginEntry)\n );\n\n const themes = await this.obsidianLauncher.downloadThemes(\n (obsidianOptions.themes ?? [])\n .map(t => resolveEntry(this.rootDir, t) as ThemeEntry),\n );\n\n let appVersion = cap.browserVersion ?? cap[OBSIDIAN_CAPABILITY_KEY]?.appVersion ?? \"latest\";\n appVersion = (await this.obsidianLauncher.getVersionInfo(appVersion)).version;\n if (semver.lt(appVersion, minSupportedObsidianVersion)) {\n throw Error(`Minimum supported Obsidian version is ${minSupportedObsidianVersion}`)\n }\n\n if (isAppium(cap)) {\n let apk = getAppiumOptions(cap).app;\n if (!apk) {\n apk = await this.obsidianLauncher.downloadAndroid(appVersion);\n }\n let chromedriverDir = getAppiumOptions(cap).chromedriverExecutableDir;\n if (!chromedriverDir) {\n chromedriverDir = path.join(this.obsidianLauncher.cacheDir, 'appium-chromedriver');\n }\n\n const normalizedObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...obsidianOptions,\n plugins, themes, vault: vault,\n appVersion, installerVersion: appVersion,\n emulateMobile: false,\n }\n cap[OBSIDIAN_CAPABILITY_KEY] = normalizedObsidianOptions;\n cap['appium:app'] = apk;\n cap['appium:chromedriverExecutableDir'] = chromedriverDir;\n cap[\"wdio:enforceWebDriverClassic\"] = true; // BiDi doesn't seem to work on Obsidian mobile\n if (!getAppiumOptions(cap)['noReset']) {\n console.warn(\"Note: For best performance set noReset to true, wdio-obsidian-service will handle resetting Obsidian between tests.\")\n }\n } else {\n const [, installerVersion] = await this.obsidianLauncher.resolveVersion(\n appVersion, obsidianOptions.installerVersion ?? \"earliest\",\n );\n const installerInfo = await this.obsidianLauncher.getInstallerInfo(installerVersion);\n\n let installerPath: string;\n if (obsidianOptions.binaryPath) {\n installerPath = path.resolve(this.rootDir, obsidianOptions.binaryPath)\n } else {\n installerPath = await this.obsidianLauncher.downloadInstaller(installerVersion);\n }\n let appPath: string;\n if (obsidianOptions.appPath) {\n appPath = path.resolve(this.rootDir, obsidianOptions.appPath)\n } else {\n appPath = await this.obsidianLauncher.downloadApp(appVersion);\n }\n let chromedriverPath = cap['wdio:chromedriverOptions']?.binary;\n // wdio can't download chromedriver for versions less than 115 automatically. Fetching it ourselves is\n // also a bit faster as it skips the chromedriver version detection step.\n if (!chromedriverPath) {\n chromedriverPath = await this.obsidianLauncher.downloadChromedriver(installerVersion);\n }\n\n const normalizedObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...obsidianOptions,\n plugins, themes, vault,\n binaryPath: installerPath, appPath: appPath,\n appVersion, installerVersion,\n emulateMobile: obsidianOptions.emulateMobile ?? false,\n }\n\n cap.browserName = \"chrome\";\n cap.browserVersion = installerInfo.chrome;\n cap[OBSIDIAN_CAPABILITY_KEY] = normalizedObsidianOptions;\n cap['goog:chromeOptions'] = {\n binary: installerPath,\n windowTypes: [\"app\", \"webview\"],\n ...cap['goog:chromeOptions'],\n args: [\n // Workaround for SUID issue on linux. See https://github.com/electron/electron/issues/42510\n ...(process.platform == 'linux' ? [\"--no-sandbox\"] : []),\n ...(cap['goog:chromeOptions']?.args ?? [])\n ],\n }\n cap['wdio:chromedriverOptions'] = {\n // allowedIps is not included in the types, but gets passed as --allowed-ips to chromedriver.\n // It defaults to [\"0.0.0.0\"] which makes Windows Firewall complain, and we don't need remote\n // connections anyways.\n allowedIps: [],\n ...cap['wdio:chromedriverOptions'],\n binary: chromedriverPath,\n } as WebdriverIO.ChromedriverOptions;\n cap[\"wdio:enforceWebDriverClassic\"] = true; // electron doesn't support BiDi yet.\n }\n }\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n}\n\n\n/**\n * wdio worker service.\n * Use in wdio.conf.mts like so:\n * ```ts\n * services: ['obsidian'],\n * ```\n * @hidden\n */\nexport class ObsidianWorkerService implements Services.ServiceInstance {\n private obsidianLauncher: ObsidianLauncher\n private browser: WebdriverIO.Browser|undefined;\n /** Directories to clean up after the tests */\n private tmpDirs: string[]\n /** Path on Android devices to store temporary vaults */\n private androidVaultDir = \"/storage/emulated/0/Documents/wdio-obsidian-service-vaults\";\n\n constructor (\n public options: ObsidianServiceOptions,\n public capabilities: WebdriverIO.Capabilities,\n public config: Options.Testrunner\n ) {\n this.obsidianLauncher = new ObsidianLauncher({\n cacheDir: config.cacheDir ?? getDefaultCacheDir(config.rootDir || process.cwd()),\n versionsUrl: options.versionsUrl,\n communityPluginsUrl: options.communityPluginsUrl, communityThemesUrl: options.communityThemesUrl,\n });\n this.tmpDirs = [];\n }\n\n /**\n * Creates a copy of the vault with plugins and themes installed\n */\n private async setupVault(cap: WebdriverIO.Capabilities) {\n const obsidianOptions = getNormalizedObsidianOptions(cap);\n let vaultCopy: string|undefined;\n if (obsidianOptions.vault != undefined) {\n log.info(`Opening vault ${obsidianOptions.vault}`);\n vaultCopy = await this.obsidianLauncher.setupVault({\n vault: obsidianOptions.vault,\n copy: true,\n plugins: obsidianOptions.plugins,\n themes: obsidianOptions.themes,\n });\n this.tmpDirs.push(vaultCopy);\n } else {\n log.info(`Opening Obsidian without a vault`)\n }\n obsidianOptions.vaultCopy = vaultCopy; // for use in getVaultPath() and the other service hooks\n }\n\n /**\n * Sets up the --user-data-dir for the Electron app. Sets the obsidian.json in the dir to open the vault on boot.\n */\n private async electronSetupConfigDir(cap: WebdriverIO.Capabilities) {\n const obsidianOptions = getNormalizedObsidianOptions(cap);\n const configDir = await this.obsidianLauncher.setupConfigDir({\n appVersion: obsidianOptions.appVersion, installerVersion: obsidianOptions.installerVersion,\n appPath: obsidianOptions.appPath,\n vault: obsidianOptions.vaultCopy,\n // `app.emulateMobile` just sets this localStorage variable. Setting it ourselves here instead of calling\n // the function simplifies the boot/plugin load sequence and makes sure plugins load in mobile mode.\n localStorage: obsidianOptions.emulateMobile ? {\"EmulateMobile\": \"1\"} : {},\n });\n this.tmpDirs.push(configDir);\n\n cap['goog:chromeOptions'] = {\n ...cap['goog:chromeOptions'],\n args: [\n `--user-data-dir=${configDir}`,\n ...(cap['goog:chromeOptions']?.args ?? []).filter(arg => {\n const match = arg.match(/^--user-data-dir=(.*)$/);\n return !match || !this.tmpDirs.includes(match[1]);\n })\n ]\n }\n }\n\n /**\n * Sets up the Obsidian app. Installs and launches it if needed, and sets permissions and contexts.\n * \n * You can configure Appium to install and launch the app for you. However, if you do that it reboots the app after\n * every session/spec which is really slow. So we are handling the app setup manually here.\n */\n private async appiumSetupApp() {\n const browser = this.browser!;\n const appiumOptions = getAppiumOptions(browser.requestedCapabilities);\n const obsidianOptions = getNormalizedObsidianOptions(browser.requestedCapabilities);\n const appId = \"md.obsidian\";\n\n // install/reinstall Obsidian if needed\n const dumpsys: string = await browser.execute(\"mobile: shell\", {command: \"dumpsys\", args: [\"package\", appId]});\n const installedObsidianVersion = dumpsys.match(/versionName[=:](.*)$/m)?.[1]?.trim();\n if (installedObsidianVersion != obsidianOptions.appVersion) {\n await browser.execute('mobile: terminateApp', {appId}); // terminate app (if running)\n await browser.execute('mobile: removeApp', {appId, keepData: false}); // uninstall (if present)\n await browser.execute('mobile: installApp', {\n appPath: appiumOptions.app, // the APK\n timeout: appiumOptions.androidInstallTimeout, // respect appium configuration\n grantPermissions: true, // this and autoGrantPermissions don't seem to really work, see below\n });\n }\n\n // start app if needed\n const appState: number = await browser.execute(\"mobile: queryAppState\", {appId});\n if (appState < 4) { // 0: not installed, 1: not running, 3: running in background, 4: running in foreground\n await browser.execute(\"mobile: activateApp\", {appId});\n }\n\n // grant Obsidian the permissions it needs, mainly file access.\n // appium:autoGrantPermissions is supposed to automatically grant everything it needs, but I can't get it to\n // work. The \"mobile: changePermissions\" \"all\" option also doesn't seem to work here. I have to explicitly list\n // the permissions and use the \"appops\" target. See https://github.com/appium/appium/issues/19991\n // I'm calling \"mobile: changePermissions\" \"all\" as well just in case it is actually doing something\n await browser.execute(\"mobile: changePermissions\", {\n action: \"grant\",\n appPackage: appId,\n permissions: \"all\",\n });\n await browser.execute(\"mobile: changePermissions\", {\n action: \"allow\",\n appPackage: appId,\n permissions: [ // these are from apk AndroidManifest.xml (extracted with apktool)\n \"READ_EXTERNAL_STORAGE\", \"WRITE_EXTERNAL_STORAGE\", \"MANAGE_EXTERNAL_STORAGE\",\n ],\n target: \"appops\", // requires appium --allow-insecure adb_shell\n });\n\n // switch to the webview context\n const context = \"WEBVIEW_md.obsidian\";\n await browser.waitUntil(async () => (await browser.getContexts() as string[]).includes(context));\n await browser.switchContext(context);\n\n // Clear app state\n await browser.execute(() => { // in case tests get interrupted during the `_capacitor_file_` bit\n if (window.location.href != \"http://localhost/\") {\n window.location.replace('http://localhost/');\n }\n })\n if (!obsidianOptions.vault) { // skip if vault is set, as we'll clear stat when we open the new vault\n await this.appiumCloseVault();\n }\n }\n\n /**\n * Opens the vault in appium.\n */\n private async appiumOpenVault() {\n const browser = this.browser!;\n const obsidianOptions = getNormalizedObsidianOptions(browser.requestedCapabilities);\n const androidVault = `${this.androidVaultDir}/${path.basename(obsidianOptions.vaultCopy!)}`;\n // TODO: Capabilities is not really the right place to be storing state like vaultCopy and uploadVault\n obsidianOptions.uploadedVault = androidVault;\n // transfer the vault to the device\n await appiumUploadFiles(browser, {src: obsidianOptions.vaultCopy!, dest: androidVault});\n\n // open vault by setting the localStorage keys and relaunching Obsidian\n // on appium restarting the app with appium:fullReset is *really* slow. And, unlike electron we can actually\n // switch vault with just a reload. So for Appium, instead of rebooting we manually wipe localStorage and use\n // reload to switch the vault.\n await browser.execute(async (androidVault) => {\n localStorage.clear();\n localStorage.setItem('mobile-external-vaults', JSON.stringify([androidVault]));\n localStorage.setItem('mobile-selected-vault', androidVault);\n // appId on mobile is just the full vault path\n localStorage.setItem(`enable-plugin-${androidVault}`, 'true');\n window.location.reload();\n }, androidVault);\n }\n\n /**\n * Close any open vault and go back to the vault switcher\n */\n private async appiumCloseVault() {\n const browser = this.browser!;\n await browser.execute(async () => {\n if (localStorage.length > 0) { // skip if we're already on the vault switcher\n localStorage.clear();\n location.reload();\n }\n });\n }\n\n /**\n * Waits for Obsidian to be ready, and does some other final setup.\n */\n private async prepareApp() {\n const browser = this.browser!;\n const obsidianOptions = getNormalizedObsidianOptions(browser.requestedCapabilities);\n\n if (obsidianOptions.emulateMobile && obsidianOptions.vault != undefined) {\n // I don't think this is technically necessary, but when you set the window size via emulateMobile it sets\n // the window size in the browser view, but not the actual window size which looks weird and makes it hard\n // to manually debug a paused test. The normal ways to set window size don't work on Obsidian. Obsidian\n // doesn't respect the `--window-size` argument, and wdio setViewport and setWindowSize don't work without\n // BiDi. This resizes the window directly using electron APIs.\n await browser.waitUntil(() => browser.execute(() => !!(window as any).electron));\n const [width, height] = await browser.execute(() => [window.innerWidth, window.innerHeight]);\n await browser.execute(async (width, height) => {\n await (window as any).electron.remote.getCurrentWindow().setSize(width, height);\n }, width, height);\n }\n\n // wait until app is loaded\n if (obsidianOptions.vault) {\n await browser.waitUntil( // wait until the helper plugin is loaded\n () => browser.execute(() => !!(window as any).wdioObsidianService),\n {timeout: 30 * 1000, interval: 100},\n );\n await browser.executeObsidian(async ({app}) => {\n await new Promise<void>((resolve) => app.workspace.onLayoutReady(resolve));\n });\n } else {\n await browser.execute(async () => {\n if (document.readyState === \"loading\") {\n return new Promise<void>(resolve => document.addEventListener(\"DOMContentLoaded\", () => resolve()));\n }\n });\n }\n }\n\n private createReloadObsidian() {\n const service = this; // eslint-disable-line @typescript-eslint/no-this-alias\n const reloadObsidian: WebdriverIO.Browser['reloadObsidian'] = async function(\n this: WebdriverIO.Browser,\n {vault, plugins, theme} = {},\n ) {\n const oldObsidianOptions = getNormalizedObsidianOptions(this.requestedCapabilities);\n const selectedPlugins = selectPlugins(oldObsidianOptions.plugins, plugins);\n const selectedThemes = selectThemes(oldObsidianOptions.themes, theme);\n if (!vault && oldObsidianOptions.vaultCopy == undefined) {\n throw Error(`No vault is open, pass a vault path to reloadObsidian`);\n }\n const newObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...oldObsidianOptions,\n // Resolve relative to PWD instead of root dir during tests\n vault: vault ? path.resolve(vault) : oldObsidianOptions.vault,\n plugins: selectedPlugins, themes: selectedThemes,\n };\n\n if (isAppium(this.requestedCapabilities)) {\n this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] = newObsidianOptions;\n if (vault) {\n await service.setupVault(this.requestedCapabilities);\n await service.appiumOpenVault();\n } else {\n // reload without resetting app state or triggering appium:fullReset\n\n // hack to disable the Obsidian app and make sure it doesn't write to the vault while we modify it\n await this.execute(() => {\n window.location.replace('http://localhost/_capacitor_file_/not-a-file');\n });\n\n // while Obsidian is down, modify the vault files to setup plugins and themes\n const local = path.join(oldObsidianOptions.vaultCopy!, \".obsidian\");\n const localCommunityPlugins = path.join(local, \"community-plugins.json\");\n const localAppearance = path.join(local, \"appearance.json\");\n const remote = `${oldObsidianOptions.uploadedVault!}/.obsidian`;\n const remoteCommunityPlugins = `${remote}/community-plugins.json`;\n const remoteAppearance = `${remote}/appearance.json`;\n\n await appiumDownloadFile(this, remoteCommunityPlugins, localCommunityPlugins).catch(() => {});\n await appiumDownloadFile(this, remoteAppearance, localAppearance).catch(() => {});\n await service.obsidianLauncher.setupVault({\n vault: oldObsidianOptions.vaultCopy!, copy: false,\n plugins: selectedPlugins, themes: selectedThemes,\n });\n let files = [localCommunityPlugins, localAppearance];\n files = (await Promise.all(files.map(async f => await fileExists(f) ? f : \"\"))).filter(f => f);\n await appiumUploadFiles(this, {src: local, dest: remote, files});\n\n // switch the app back\n await this.execute(() => {\n window.location.replace('http://localhost/');\n })\n }\n } else {\n // if browserName is set, reloadSession tries to restart the driver entirely, so unset those\n const newCap: WebdriverIO.Capabilities = _.cloneDeep(\n _.omit(this.requestedCapabilities, ['browserName', 'browserVersion'])\n );\n newCap[OBSIDIAN_CAPABILITY_KEY] = newObsidianOptions;\n\n if (vault) {\n await service.setupVault(newCap);\n await service.electronSetupConfigDir(newCap);\n await this.reloadSession(newCap);\n } else {\n // reload preserving current vault and config dir\n\n // Obsidian debounces saves to the config dir, and so changes to configuration made in the tests may\n // not get saved to disk before the reboot. I haven't found a better way to flush everything than\n // just waiting a bit.\n await this.pause(2000);\n\n await this.deleteSession({shutdownDriver: false});\n // while Obsidian is down, modify the vault files to setup plugins and themes\n await service.obsidianLauncher.setupVault({\n vault: oldObsidianOptions.vaultCopy!, copy: false,\n plugins: selectedPlugins, themes: selectedThemes,\n });\n await this.reloadSession(newCap);\n }\n }\n await service.prepareApp();\n };\n return reloadObsidian;\n }\n\n /**\n * Runs before the session and browser have started.\n */\n async beforeSession(config: Options.Testrunner, cap: WebdriverIO.Capabilities) {\n try {\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n if (cap[OBSIDIAN_CAPABILITY_KEY].vault != undefined) {\n await this.setupVault(cap);\n }\n if (!isAppium(cap)) {\n await this.electronSetupConfigDir(cap);\n }\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n\n /**\n * Runs after session and browser have started, but before tests.\n */\n async before(cap: WebdriverIO.Capabilities, specs: unknown, browser: WebdriverIO.Browser) {\n this.browser = browser;\n try {\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n\n // You are supposed to add commands via the addCommand hook, however you can't add synchronous methods that\n // way. Also, addCommand completely breaks the stack traces of errors from the methods, while tacking on the\n // methods manually doesn't.\n const newBrowserCommands = {\n ...browserCommands,\n reloadObsidian: this.createReloadObsidian(),\n }\n for (const [name, cmd] of Object.entries(newBrowserCommands)) {\n (browser as any)[name] = cmd;\n }\n\n if (isAppium(browser.requestedCapabilities)) {\n await this.appiumSetupApp();\n if (cap[OBSIDIAN_CAPABILITY_KEY].vault) {\n await this.appiumOpenVault();\n }\n }\n await this.prepareApp();\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n\n /** Runs after tests are done, but before the session is shut down */\n async after(result: number, cap: WebdriverIO.Capabilities) {\n const browser = this.browser!;\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n\n if (isAppium(cap)) {\n await this.appiumCloseVault();\n // \"mobile: deleteFile\" doesn't work on folders. \"mobile:shell\" requires appium --allow-insecure adb_shell\n await browser.execute(\"mobile: shell\", {command: \"rm\", args: [\"-rf\", this.androidVaultDir]});\n }\n }\n\n /**\n * Cleanup\n */\n async afterSession() {\n for (const tmpDir of this.tmpDirs) {\n await fsAsync.rm(tmpDir, { recursive: true, force: true });\n }\n }\n}\n","import * as path from \"path\"\nimport * as fsAsync from \"fs/promises\"\nimport * as crypto from \"crypto\";\nimport { fileURLToPath } from \"url\";\nimport { TFile } from \"obsidian\";\nimport _ from \"lodash\";\nimport { OBSIDIAN_CAPABILITY_KEY, NormalizedObsidianCapabilityOptions } from \"../types.js\";\nimport { BasePage } from \"./basePage.js\";\nimport { isAppium, normalizePath, isHidden, isText } from \"../utils.js\";\nimport { AppInternal } from \"../obsidianTypes.js\";\n\n\n/**\n * Class with various helper methods for writing Obsidian tests using the\n * [page object pattern](https://webdriver.io/docs/pageobjects).\n * \n * You can get an instance of this class either by running\n * ```ts\n * const obsidianPage = await browser.getObsidianPage();\n * ```\n * or just importing it directly with\n * ```ts\n * import { obsidianPage } from \"wdio-obsidian-service\";\n * ```\n * \n * @hideconstructor\n * @category Utilities\n */\nclass ObsidianPage extends BasePage {\n private getObsidianCapabilities(): NormalizedObsidianCapabilityOptions {\n return this.browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];\n }\n\n /**\n * Returns the path to the vault opened in Obsidian.\n * \n * wdio-obsidian-service copies your vault before running tests, so this is the path to the temporary copy.\n */\n getVaultPath(): string {\n const obsidianOptions = this.getObsidianCapabilities();\n if (obsidianOptions.vaultCopy === undefined) {\n throw Error(\"No vault open, set vault in wdio.conf.mts or use reloadObsidian to open a vault dynamically.\")\n }\n return obsidianOptions.uploadedVault ?? obsidianOptions.vaultCopy;\n }\n\n /**\n * Return the Obsidian config dir (just \".obsidian\" unless you changed the config dir name in settings).\n */\n async getConfigDir(): Promise<string> {\n return await this.browser.executeObsidian(({app}) => app.vault.configDir)\n }\n\n /**\n * Returns the Obsidian Platform object. Useful for skipping tests based on whether you are running in desktop or\n * mobile, or based on OS.\n */\n async getPlatform(): Promise<Platform> {\n const obsidianOptions = this.getObsidianCapabilities();\n if (obsidianOptions.vault !== undefined) {\n return await this.browser.executeObsidian(({obsidian}) => {\n const p = obsidian.Platform;\n return {\n isDesktop: p.isDesktop,\n isMobile: p.isMobile,\n isDesktopApp: p.isDesktopApp,\n isMobileApp: p.isMobileApp,\n isIosApp: p.isIosApp,\n isAndroidApp: p.isAndroidApp,\n isPhone: p.isPhone,\n isTablet: p.isTablet,\n isMacOS: p.isMacOS,\n isWin: p.isWin,\n isLinux: p.isLinux,\n isSafari: p.isSafari,\n };\n });\n } else {\n // hack to allow calling getPlatform before opening a vault. This is needed so you can use getPlatform to\n // skip a test before wasting time opening the vault. we don't use this method the rest of the time as you\n // can technically change the size or switch emulation mode during tests\n const appium = isAppium(this.browser.requestedCapabilities);\n const emulateMobile = obsidianOptions.emulateMobile;\n const [width, height] = await this.browser.execute(() => [window.innerWidth, window.innerHeight]);\n\n let isTablet = false;\n let isPhone = false;\n if (appium || emulateMobile) {\n // replicate Obsidian's tablet vs phone breakpoint\n isTablet = (width >= 600 && height >= 600);\n isPhone = !isTablet;\n }\n\n return {\n isDesktop: !(appium || emulateMobile),\n isMobile: appium || emulateMobile,\n isDesktopApp: !appium,\n isMobileApp: appium,\n isIosApp: false, // iOS is not supported\n isAndroidApp: appium,\n isPhone: isPhone,\n isTablet: isTablet,\n isMacOS: !appium && process.platform == 'darwin',\n isWin: !appium && process.platform == 'win32',\n isLinux: !appium && process.platform == 'linux',\n isSafari: false, // iOS is not supported\n };\n }\n }\n\n /**\n * Enables a plugin by ID\n */\n async enablePlugin(pluginId: string): Promise<void> {\n await this.browser.executeObsidian(\n async ({app}, pluginId) => await (app as AppInternal).plugins.enablePluginAndSave(pluginId),\n pluginId,\n );\n }\n\n /**\n * Disables a plugin by ID\n */\n async disablePlugin(pluginId: string): Promise<void> {\n await this.browser.executeObsidian(\n async ({app}, pluginId) => await (app as AppInternal).plugins.disablePluginAndSave(pluginId),\n pluginId,\n );\n }\n\n /**\n * Sets the theme. Pass \"default\" to reset to the Obsidian theme.\n */\n async setTheme(themeName: string): Promise<void> {\n themeName = themeName == 'default' ? '' : themeName;\n await this.browser.executeObsidian(\n async ({app}, themeName) => await (app as AppInternal).customCss.setTheme(themeName),\n themeName,\n )\n }\n\n /**\n * Opens a file in a new tab.\n */\n async openFile(path: string) {\n await this.browser.executeObsidian(async ({app, obsidian}, path) => {\n const file = app.vault.getAbstractFileByPath(path);\n if (file instanceof obsidian.TFile) {\n const leaf = app.workspace.getLeaf('tab');\n await leaf.openFile(file);\n app.workspace.setActiveLeaf(leaf, {focus: true});\n } else {\n throw Error(`No file ${path} exists`);\n }\n }, path)\n }\n\n /**\n * Loads a saved workspace layout from `.obsidian/workspaces.json` by name. Use the core \"Workspaces\"\n * plugin to create the layouts. You can also pass the layout object directly.\n */\n async loadWorkspaceLayout(layout: any): Promise<void> {\n if (typeof layout == \"string\") {\n // read from .obsidian/workspaces.json like the built-in workspaces plugin does\n const workspacesPath = `${await this.getConfigDir()}/workspaces.json`;\n const layoutName = layout;\n try {\n const fileContent = await this.browser.executeObsidian(async ({app}, workspacesPath) => {\n return await app.vault.adapter.read(workspacesPath);\n }, workspacesPath);\n layout = JSON.parse(fileContent)?.workspaces?.[layoutName];\n } catch {\n throw new Error(`Failed to load ${workspacesPath}:${layoutName}`);\n }\n if (!layout) {\n throw new Error(`No workspace ${layoutName} found in ${workspacesPath}`);\n }\n }\n\n await this.browser.executeObsidian(async ({app}, layout) => {\n await app.workspace.changeLayout(layout)\n }, layout)\n }\n\n /**\n * Deletes a file or folder in the vault.\n * @param file path of inside vault, e.g. \"/books/leviathan-wakes.md\"\n */\n async delete(file: string) {\n await this.browser.executeObsidian(async ({app, obsidian}, file, isVaultFile) => {\n file = obsidian.normalizePath(file);\n if (isVaultFile) {\n const fileObj = app.vault.getAbstractFileByPath(file);\n if (fileObj) {\n await app.vault.delete(fileObj, true);\n };\n } else {\n const stat = await app.vault.adapter.stat(file);\n if (stat && stat.type == \"folder\") {\n await app.vault.adapter.rmdir(file, true);\n } else if (stat) {\n await app.vault.adapter.remove(file);\n }\n }\n }, file, !isHidden(file));\n }\n\n /**\n * Writes to a file in the vault. Creates parent directories if needed.\n * @param file path of inside vault, e.g. \"/books/leviathan-wakes.md\"\n * @param content content to write to the file\n */\n async write(file: string, content: string|ArrayBuffer) {\n let strContent: string|undefined, binContent: string|undefined;\n if (typeof content == \"string\") {\n strContent = content;\n } else {\n binContent = Buffer.from(content).toString(\"base64\");\n }\n await this.browser.executeObsidian(async ({app, obsidian}, file, isVaultFile, strContent, binContent) => {\n file = obsidian.normalizePath(file);\n const parent = file.split(\"/\").slice(0, -1).join(\"/\");\n // there's a bug in Obsidian Android where occasionally creating a file silently fails, leaving just an\n // empty file. So retry if that happens. See https://forum.obsidian.md/t/102935\n let success = false;\n let retries = 0;\n while (!success && retries < 8) {\n if (isVaultFile) {\n if (parent && !app.vault.getAbstractFileByPath(parent)) {\n await app.vault.createFolder(parent);\n }\n const fileObj = app.vault.getAbstractFileByPath(file);\n strContent = strContent ?? atob(binContent!);\n if (fileObj) {\n await app.vault.modify(fileObj as TFile, strContent);\n } else {\n await app.vault.create(file, strContent);\n }\n } else {\n await app.vault.adapter.mkdir(parent);\n if (strContent) {\n await app.vault.adapter.write(file, strContent);\n } else {\n const buffer = Uint8Array.from(atob(binContent!), c => c.charCodeAt(0)).buffer;\n await app.vault.adapter.writeBinary(file, buffer);\n }\n }\n\n success = (\n !obsidian.Platform.isAndroidApp ||\n (strContent?.length ?? binContent!.length) === 0 ||\n (await app.vault.adapter.stat(file))!.size > 0\n );\n retries++;\n }\n if (!success) {\n throw new Error(`Failed to write file ${file}`);\n }\n }, file, !isHidden(file) && isText(file), strContent, binContent);\n }\n\n /**\n * Create a folder in the vault. Creates parent directories if needed.\n * @param file path of inside vault, e.g. \"/books\"\n */\n async mkdir(file: string) {\n await this.browser.executeObsidian(async ({app, obsidian}, file, isVaultFile) => {\n file = obsidian.normalizePath(file);\n if (isVaultFile) {\n if (parent && !app.vault.getAbstractFileByPath(file)) {\n await app.vault.createFolder(file);\n }\n } else {\n await app.vault.adapter.mkdir(file);\n }\n }, file, !isHidden(file));\n }\n\n /**\n * Updates the vault by modifying files in place without reloading Obsidian. Can be used to reset the vault back to\n * its original state or to \"switch\" to an entirely different vault without rebooting Obsidian\n * \n * This will only update regular vault files, it won't touch anything under `.obsidian`, and it won't reset any\n * Obsidian config or plugin settings. But if all you need is to reset the vault files, this can be used as a faster\n * alternative to {@link ObsidianBrowserCommands.reloadObsidian | reloadObsidian}.\n * \n * You'll often want to combine resetVault with something like this to reset your plugin's configuration as well:\n * ```ts\n * await browser.executeObsidian(async ({plugins}, settings) => {\n * Object.assign(plugins.myPlugin.settings, settings);\n * await plugins.myPlugin.saveSettings();\n * }, {...});\n * ```\n *\n * If no vault is passed, it resets the vault back to the oringal vault opened by the tests. You can also pass a\n * path to a different vault, and it will replace the current files with the files of that vault (similar to an\n * \"rsync\"). Or, instead of passing a vault path you can pass an object mapping vault file paths to file content.\n * E.g.\n * ```ts\n * obsidianPage.resetVault({\n * 'path/in/vault.md': \"Hello World\",\n * })\n * ```\n * \n * You can also pass multiple vaults and objects, and they will be merged. This can be useful if you want to add a\n * few small modifications to the base vault. e.g:\n * ```ts\n * obsidianPage.resetVault('./path/to/vault', {\n * \"books/leviathan-wakes.md\": \"...\",\n * })\n * ```\n */\n async resetVault(...vaults: (string|Record<string, string|ArrayBuffer>)[]) {\n const obsidianOptions = this.getObsidianCapabilities();\n if (!obsidianOptions.vault) {\n // open an empty vault if there's no vault open\n const defaultVaultPath = path.resolve(fileURLToPath(import.meta.url), '../../default-vault');\n await this.browser.reloadObsidian({vault: defaultVaultPath});\n }\n const configDir = await this.getConfigDir();\n vaults = vaults.length == 0 ? [obsidianOptions.vault!] : vaults;\n\n // list all files in the new vault\n type NewFileInfo = {type: \"file\"|\"folder\", sourceContent?: string|ArrayBuffer, sourceFile?: string};\n const newVault: Map<string, NewFileInfo> = new Map();\n for (let vault of vaults) {\n if (typeof vault == \"string\") {\n vault = path.resolve(vault);\n const files = await fsAsync.readdir(vault, { recursive: true, withFileTypes: true });\n for (const f of files) {\n const fullPath = path.join(f.parentPath, f.name);\n const vaultPath = path.relative(vault, fullPath).split(path.sep).join(\"/\");\n if (!vaultPath.startsWith(configDir + \"/\")) {\n if (f.isDirectory()) {\n newVault.set(vaultPath, {type: 'folder'});\n } else {\n newVault.set(vaultPath, {type: 'file', sourceFile: fullPath});\n }\n }\n }\n } else {\n vault = _.mapKeys(vault, (v, k) => normalizePath(k));\n for (const [file, content] of Object.entries(vault)) {\n newVault.set(file, {type: \"file\", sourceContent: content});\n }\n const folders = new Set(Object.keys(vault).map(p => path.posix.dirname(p)).filter(p => p !== \".\"));\n for (const folder of folders) {\n newVault.set(folder, {type: \"folder\"});\n }\n }\n }\n\n // list all files in the current vault\n type CurrFileInfo = {type: \"file\"|\"folder\", hash?: string};\n const currVault = new Map(await this.browser.executeObsidian(({app}, configDir) => {\n async function hash(data: ArrayBuffer) {\n const hashBuffer = await window.crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n }\n\n async function listRecursive(path: string): Promise<[string, CurrFileInfo][]> {\n const result: [string, CurrFileInfo][] = [];\n const { folders, files } = await app.vault.adapter.list(path);\n for (const folder of folders) {\n if (!folder.startsWith(configDir + \"/\")) {\n result.push([folder, {type: \"folder\"}]);\n result.push(...await listRecursive(folder));\n }\n }\n for (const file of files) {\n if (!file.startsWith(configDir + \"/\")) {\n let fileHash = (app as AppInternal).metadataCache.getFileInfo(file)?.hash;\n if (!fileHash) { // hidden files etc. aren't in Obsidian's metadata cache\n fileHash = await hash(await app.vault.adapter.readBinary(file));\n }\n result.push([file, { type: \"file\", hash: fileHash }]);\n }\n }\n return result;\n }\n\n return listRecursive(\"/\");\n }, configDir));\n\n // delete any files that need to be deleted\n for (const file of [...currVault.keys()].sort().reverse()) {\n const currFileInfo = currVault.get(file)!\n if (!newVault.has(file) || newVault.get(file)!.type != currFileInfo.type) {\n await this.delete(file);\n }\n }\n\n // create files and folders\n for (const [file, newFileInfo] of _.sortBy([...newVault.entries()], 0)) {\n const currFileInfo = currVault.get(file);\n if (newFileInfo.type == \"file\") {\n let content = newFileInfo.sourceContent;\n if (!content) {\n content = (await fsAsync.readFile(newFileInfo.sourceFile!)).buffer as ArrayBuffer;\n }\n const hash = crypto.createHash(\"SHA256\")\n .update(typeof content == \"string\" ? content : new Uint8Array(content))\n .digest(\"hex\");\n if (!currFileInfo || currFileInfo.hash != hash) {\n await this.write(file, content);\n }\n } else if (newFileInfo.type == \"folder\" && !currFileInfo) {\n await this.mkdir(file);\n }\n }\n }\n}\n\n/**\n * Info on the platform we are running on or emulating, in similar format as\n * [obsidian.Platform](https://docs.obsidian.md/Reference/TypeScript+API/Platform)\n * @category Types\n */\nexport interface Platform {\n /**\n * The UI is in desktop mode.\n */\n isDesktop: boolean;\n /**\n * The UI is in mobile mode.\n */\n isMobile: boolean;\n /**\n * We're running the Electron-based desktop app.\n * Note, when running under `emulateMobile` this will still be true and isDesktop will be false.\n */\n isDesktopApp: boolean;\n /**\n * We're running the Capacitor-js mobile app.\n * Note, when running under `emulateMobile` this will still be false and isMobile will be true.\n */\n isMobileApp: boolean;\n /** \n * We're running the iOS app.\n * Note, wdio-obsidian-service doesn't support iOS yet, so this will always be false.\n */\n isIosApp: boolean;\n /**\n * We're running the Android app.\n */\n isAndroidApp: boolean;\n /**\n * We're in a mobile app that has very limited screen space.\n */\n isPhone: boolean;\n /**\n * We're in a mobile app that has sufficiently large screen space.\n */\n isTablet: boolean;\n /**\n * We're on a macOS device, or a device that pretends to be one (like iPhones and iPads).\n * Typically used to detect whether to use command-based hotkeys vs ctrl-based hotkeys.\n */\n isMacOS: boolean;\n /**\n * We're on a Windows device.\n */\n isWin: boolean;\n /**\n * We're on a Linux device.\n */\n isLinux: boolean;\n /**\n * We're running in Safari.\n * Note, wdio-obsidian-service doesn't support iOS yet, so this will always be false.\n */\n isSafari: boolean;\n};\n\n/**\n * Instance of {@link ObsidianPage} with helper methods for writing Obsidian tests.\n * @category Utilities\n */\nconst obsidianPage = new ObsidianPage()\nexport default obsidianPage;\nexport { ObsidianPage };\n","import type { ObsidianBrowserCommands } from \"./browserCommands.js\";\nimport type { PluginEntry, ThemeEntry, DownloadedPluginEntry, DownloadedThemeEntry } from \"obsidian-launcher\";\n\nexport const OBSIDIAN_CAPABILITY_KEY = \"wdio:obsidianOptions\";\n\n/**\n * Options passed to an \"wdio:obsidianOptions\" capability in wdio.conf.mts. E.g.\n * ```ts\n * // ...\n * capabilities: [{\n * browserName: \"obsidian\",\n * browserVersion: \"latest\",\n * 'wdio:obsidianOptions': {\n * installerVersion: 'earliest',\n * plugins: [\".\"],\n * },\n * }],\n * ```\n * \n * @category Options\n */\nexport interface ObsidianCapabilityOptions {\n /**\n * Version of Obsidian to download and run.\n * \n * Can be set to a specific version or one of:\n * - \"latest\": Run the latest non-beta Obsidian version\n * - \"latest-beta\": Run the latest beta Obsidian version (or latest if there is no current beta)\n * - To download Obsidian beta versions you'll need to have an Obsidian Insiders account and either set the \n * `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` env vars (`.env` file is supported) or pre-download the Obsidian \n * beta version with `npx obsidian-launcher download app -v latest-beta`\n * - \"earliest\": Run the `minAppVersion` set in your `manifest.json`\n * \n * Defaults to \"latest\".\n * \n * You can also use the wdio capability `browserVersion` field to set the Obsidian version.\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n */\n appVersion?: string\n\n /**\n * Version of the Obsidian installer to download and run.\n * \n * Obsidian is Desktop distributed in two parts, the app which contains the JS, and the installer which is the\n * binary with Electron. Obsidian's auto update only updates the app, so users on the same Obsidian version can be\n * running different Electron versions. You can use this to test your plugin against different installer/Electron\n * versions.\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n *\n * Can be set to a specific version string or one of:\n * - \"latest\": Run the latest Obsidian installer compatible with `appVersion`.\n * - \"earliest\": Run the oldest Obsidian installer compatible with `appVersion`.\n * \n * Defaults to \"earliest\".\n */\n installerVersion?: string,\n\n /**\n * List of plugins to install.\n * \n * Each entry is a path to the local plugin to install, e.g. [\".\"] or [\"dist\"] depending on your build setup. Paths\n * are relative to your `wdio.conf.mts`. You can also pass objects. If you pass an object it should contain one of\n * `path` (to install a local plugin), `repo` (to install a plugin from GitHub), or `id` (to install a community\n * plugin). You can set `enabled: false` to install the plugin but start it disabled. You can enable the plugin\n * later using {@link ObsidianBrowserCommands.reloadObsidian|browser.reloadObsidian} or\n * {@link ObsidianPage.enablePlugin}.\n */\n plugins?: PluginEntry[],\n\n /**\n * List of themes to install.\n * \n * Each entry is a path to the local theme to install. Paths are relative to your `wdio.conf.mts`. You can also pass\n * an object. If you pass an object it should contain one of `path` (to install a local theme), `repo` (to install a\n * theme from GitHub), or `name` (to install a community theme). You can set `enabled: false` to install the theme,\n * but start it disabled. You can only have one enabled theme, so if you pass multiple you'll have to disable all\n * but one.\n */\n themes?: ThemeEntry[],\n\n /**\n * The path to the vault to open.\n * \n * The vault will be copied, so any changes made in your tests won't affect the original. If omitted, no vault will\n * be opened and you'll need to call {@link ObsidianBrowserCommands.reloadObsidian|browser.reloadObsidian} to open a\n * vault during your tests. Path is relative to your `wdio.conf.mts`.\n */\n vault?: string,\n\n /**\n * Set to true to emulate mobile on the Electron desktop app. This uses Obsidian `app.emulateMobile()` to switch\n * Obsidian to the mobile UI, and you can use Chrome's mobileEmulation to set the screen size. You can compare\n * tablet vs phone UIs by setting the screen size or emulated device. Obsidian tablet UI triggers at\n * width/height >= 600.\n *\n * Note that Obsidian Mobile runs on Capacitor instead of Electron so there are various platform differences that\n * can't be emulated. But it's good enough for most cases as long as you aren't interacting directly with the\n * operating system or Electron APIs. You can use an Android Virtual Device instead if you want a more accurate\n * (but slower) mobile test.\n *\n * See [Mobile Emulation](../README.md#mobile-emulation) for more info.\n * See [Android](../README.md#android) if you want to test the real mobile app on an Android Virtual Device instead.\n */\n emulateMobile?: boolean,\n\n /**\n * Path to the Obsidian binary to use. If omitted it will be downloaded automatically.\n */\n binaryPath?: string,\n\n /**\n * Path to the app asar to load into obsidian. If omitted it will be downloaded automatically.\n */\n appPath?: string,\n}\n\n\n/** Internal type, capability options after being normalized by onPrepare */\nexport interface NormalizedObsidianCapabilityOptions {\n appVersion: string\n installerVersion: string,\n plugins: DownloadedPluginEntry[],\n themes: DownloadedThemeEntry[],\n vault?: string,\n vaultCopy?: string,\n /** Path of the vault on the appium device */\n uploadedVault?: string,\n emulateMobile: boolean,\n binaryPath?: string,\n appPath?: string,\n}\n\n\n/**\n * Options passed to wdio-obsidian-service service in wdio.conf.mts. E.g.\n * ```js\n * // ...\n * services: [[\"obsidian\", {versionsUrl: \"file:///path/to/obsidian-versions.json\"}]]\n * ```\n * You'll usually want to leave these options as the default, they are mostly useful for wdio-obsidian-service's\n * internal tests.\n * \n * @category Options\n */\nexport interface ObsidianServiceOptions {\n /**\n * Override the `obsidian-versions.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/jesse-r-s-hines/wdio-obsidian-service/HEAD/obsidian-versions.json\n * which is auto-updated to contain information on available Obsidian versions.\n */\n versionsUrl?: string,\n /**\n * Override the `community-plugins.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-plugins.json\n */\n communityPluginsUrl?: string,\n /**\n * Override the `community-css-themes.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-css-themes.json\n */\n communityThemesUrl?: string,\n}\n\ndeclare global {\n namespace WebdriverIO {\n interface Capabilities {\n [OBSIDIAN_CAPABILITY_KEY]?: ObsidianCapabilityOptions,\n }\n\n interface Browser extends ObsidianBrowserCommands {}\n }\n}\n","// src/index.ts\nvar globals = globalThis._wdioGlobals = globalThis._wdioGlobals || /* @__PURE__ */ new Map();\nvar GLOBALS_ERROR_MESSAGE = `No browser instance registered. Don't import @wdio/globals outside of the WDIO testrunner context. Or you have two two different \"@wdio/globals\" packages installed.`;\nfunction proxyHandler(key) {\n return {\n get: (self, prop) => {\n if (!globals.has(key)) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n const receiver = globals.get(key);\n const field = receiver[prop];\n return typeof field === \"function\" ? field.bind(receiver) : field;\n }\n };\n}\nvar browser = new Proxy(\n class Browser {\n },\n proxyHandler(\"browser\")\n);\nvar driver = new Proxy(\n class Browser2 {\n },\n proxyHandler(\"driver\")\n);\nvar multiremotebrowser = new Proxy(\n class Browser3 {\n },\n proxyHandler(\"multiremotebrowser\")\n);\nvar $ = (...args) => {\n if (!globals.has(\"$\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"$\")(...args);\n};\nvar $$ = (...args) => {\n if (!globals.has(\"$$\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"$$\")(...args);\n};\nvar expect = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\")(...args);\n};\nvar ASYNC_MATCHERS = [\n \"any\",\n \"anything\",\n \"arrayContaining\",\n \"objectContaining\",\n \"stringContaining\",\n \"stringMatching\"\n];\nfor (const matcher of ASYNC_MATCHERS) {\n expect[matcher] = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\")[matcher](...args);\n };\n}\nexpect.not = ASYNC_MATCHERS.reduce((acc, matcher) => {\n acc[matcher] = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\").not[matcher](...args);\n };\n return acc;\n}, {});\nexpect.extend = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n const expect2 = globals.get(\"expect\");\n return expect2.extend(...args);\n};\nfunction _setGlobal(key, value, setGlobal = true) {\n globals.set(key, value);\n if (setGlobal) {\n globalThis[key] = value;\n }\n}\nexport {\n $,\n $$,\n _setGlobal,\n browser,\n driver,\n expect,\n multiremotebrowser\n};\n","import * as wdioGlobals from \"@wdio/globals\"\n\n/**\n * Base page object for use in the wdio [page object pattern](https://webdriver.io/docs/pageobjects).\n * \n * You can pass the browser to the page object, which allows using the object even in wdio standalone mode.\n */\nexport class BasePage {\n private _browser: WebdriverIO.Browser|undefined\n constructor(browser?: WebdriverIO.Browser) {\n this._browser = browser;\n }\n\n /**\n * Returns the browser instance.\n * @hidden\n */\n protected get browser(): WebdriverIO.Browser {\n return this._browser ?? wdioGlobals.browser;\n }\n}\n","import path from \"path\";\nimport crypto from \"crypto\";\nimport fsAsync from \"fs/promises\";\nimport * as tar from \"tar\";\nimport _ from \"lodash\";\n\n/** Quote string for use in shell scripts */\nexport function quote(input: string) {\n return `'${input.replace(/'/g, \"'\\\\''\")}'`;\n}\n\nexport async function fileExists(path: string) {\n try {\n await fsAsync.access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Gets the appium options.\n * Handles combining `\"appium:foo\"` and `\"appium:options\": {\"foo\": ...}` style options.\n */\nexport function getAppiumOptions(\n cap: WebdriverIO.Capabilities,\n): Exclude<WebdriverIO.Capabilities['appium:options'], undefined> {\n let result = _.pickBy(cap, (v, k) => k.startsWith(\"appium:\") && k != 'appium:options');\n result = _.mapKeys(result, (v, k) => k.slice(7))\n return {...result, ...cap['appium:options']};\n}\n\n/** Returns true if this capability is for Appium */\nexport function isAppium(cap: WebdriverIO.Capabilities) {\n return getAppiumOptions(cap).automationName?.toLocaleLowerCase() === 'uiautomator2';\n}\n\n/**\n * Upload multiple files at once. Uploads files as a tar for much better performance and to avoid issues with special\n * characters in names. (see https://github.com/appium/appium-android-driver/issues/1004)\n * \n * @param opts.src Directory to copy from (-C in tar)\n * @param opts.dest Directory to copy to\n * @param opts.files Files under src to copy. Defaults to the whole dir.\n * @param opts.chunkSize Size to split the tar into while uploading (to avoid excessive RAM usage)\n */\nexport async function appiumUploadFiles(browser: WebdriverIO.Browser, opts: {\n src: string, dest: string, files?: string[], chunkSize?: number,\n}) {\n let {\n src, dest,\n files = [src],\n chunkSize = 2 * 1024 * 1024,\n } = opts;\n src = path.resolve(src);\n dest = path.posix.normalize(dest).replace(/\\/$/, '');\n files = files.map(f => path.relative(src, f) || \".\");\n if (files.length == 0) return; // nothing to do\n\n // We'll tar the files up and then extract the tar on Android side. This is a lot faster than doing many small\n // pushFiles. Since pushFile requires sending the whole file contents as a base64 string, we'll split up the tar\n // into chunks to keep the size manageable for large vaults.\n\n const tmpDir = \"/data/local/tmp\"\n const slug = crypto.randomBytes(10).toString(\"base64url\").replace(/[-_]/g, '0');\n const stream = tar.create({C: src, gzip: { level: 2 }}, files);\n\n // buffer/chunk size of the tar stream varies. Without compression it appears to be one chunk per file, splitting\n // large files at maxReadSize, plus some chunks for the header. However compression adds another layer that appears\n // to limit the output chunk size to about 16K.\n let buffers: Buffer[] = [];\n let bufferSize = 0;\n let i = 0;\n for await (const chunk of stream) {\n if (bufferSize + chunk.length > chunkSize) {\n const data = Buffer.concat(buffers).toString('base64');\n await browser.pushFile(`${tmpDir}/${slug}-${String(i).padStart(6, '0')}.tar`, data);\n i++;\n buffers = [];\n bufferSize = 0;\n }\n buffers.push(chunk);\n bufferSize += chunk.length;\n }\n const data = Buffer.concat(buffers).toString('base64');\n await browser.pushFile(`${tmpDir}/${slug}-${String(i).padStart(6, '0')}.tar`, data);\n\n // extract the tar. `mobile: shell` does NOT escape arguments despite taking an argument array.\n await browser.execute(\"mobile: shell\", {command: \"sh\", args: [\"-c\", quote(`\n mkdir -p ${quote(dest)};\n cat ${tmpDir}/${slug}-*.tar | tar -xz -C ${quote(dest)};\n rm ${tmpDir}/${slug}-*.tar;\n `)]});\n}\n\nexport async function appiumDownloadFile(browser: WebdriverIO.Browser, src: string, dest: string) {\n src = path.posix.normalize(src);\n dest = path.resolve(dest);\n const content = Buffer.from(await browser.pullFile(src), \"base64\");\n await fsAsync.writeFile(dest, content);\n}\n\n/** Lists all files under a folder. Returns full paths. */\nexport async function appiumReaddir(browser: WebdriverIO.Browser, dir: string) {\n // list all files, one per line, no escaping\n const stdout: string = await browser.execute(\"mobile: shell\", {command: \"ls\", args: [\"-NA1\", dir]});\n return stdout.split(\"\\n\").filter(f => f).map(f => path.join(dir, f)).sort();\n}\n\n/** SHA256 hash */\nexport async function hash(content: string|ArrayBufferLike) {\n return crypto.createHash(\"SHA256\")\n .update(typeof content == \"string\" ? content : new Uint8Array(content))\n .digest(\"hex\");\n}\n\n/** Replicate obsidian.normalizePath */\nexport function normalizePath(p: string) {\n p = p.replace(/([\\\\/])+/g, \"/\").replace(/(^\\/+|\\/+$)/g, \"\");\n if (p == \"\") {\n p = \"/\"\n }\n p = p.replace(/\\u00A0|\\u202F/g, \" \"); // replace special space characters\n p = p.normalize(\"NFC\");\n return p;\n}\n\n/** Returns true if a vault file path is hidden (either it or one of it's parent directories starts with \".\") */\nexport function isHidden(file: string) {\n return file.split(\"/\").some(p => p.startsWith(\".\"))\n}\n\n/** Returns true if this is a simple text file */\nexport function isText(file: string) {\n return [\".md\", \".json\", \".txt\", \".js\"].includes(path.extname(file).toLocaleLowerCase());\n}\n","import type * as obsidian from \"obsidian\"\nimport { ObsidianPage } from \"./pageobjects/obsidianPage.js\"\nimport { NormalizedObsidianCapabilityOptions, OBSIDIAN_CAPABILITY_KEY } from \"./types.js\";\nimport { AppInternal } from \"./obsidianTypes.js\";\n\nexport const browserCommands = {\n /**\n * Wrapper around browser.execute that passes the Obsidian API to the function. The first argument to the function\n * is an object containing keys:\n * - app: Obsidian app instance\n * - obsidian: Full Obsidian API\n * - plugins: Object of all installed plugins, mapped by plugin id converted to camelCase.\n * - require: The customized require function Obsidian makes available to plugins. This is also available globally,\n * so you can just use `require` directly instead of from {@link ExecuteObsidianArg} if you prefer.\n * \n * Like `brower.execute`, you can pass other extra arguments to the function.\n * \n * See also: https://webdriver.io/docs/api/browser/execute\n * \n * Example usage\n * ```ts\n * const file = browser.executeObsidian(({app, obsidian}, path) => {\n * return app.vault.getMarkdownFiles().find(f => f.path == path);\n * })\n * ```\n * \n * Note: The same caveats as `browser.execute` apply. The function will be stringified and then run inside Obsidian,\n * so you can't capture any local variables. E.g.\n * \n * This *won't* work:\n * ```ts\n * import { FileView } from \"obsidian\"\n * browser.executeObsidian(({app}) => {\n * if (leaf.view instanceof FileView) {\n * ...\n * }\n * })\n * ```\n * do this instead:\n * ```ts\n * browser.executeObsidian(({app, obsidian}) => {\n * if (leaf.view instanceof obsidian.FileView) {\n * ...\n * }\n * })\n * ```\n */\n async executeObsidian<Return, Params extends unknown[]>(\n this: WebdriverIO.Browser,\n func: (obs: ExecuteObsidianArg, ...params: Params) => Return,\n ...params: Params\n ): Promise<Return> {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n if (obsidianOptions.vault === undefined) {\n throw Error(\"No vault open, set vault in wdio.conf.mts or use reloadObsidian to open a vault dynamically.\")\n }\n // Call the function. Just stringify the function (browser.execute stringifies it anyways). Add a workaround for\n // node fs errors returning useless error messages. If the thrown error has a \"code\" field that is a string, the\n // chromedriver throws an error like this with no further info:\n // \"unknown error: call function result missing int 'status'\"\n // I think this is a bug in ChromeDevtoolsProtocol or chromedriver. See\n // https://github.com/electron-userland/spectron/issues/1057\n const result = await this.execute<Return, Params>(`\n const require = window.wdioObsidianService().require;\n try {\n return await (\n ${func.toString()}\n ).call(null, window.wdioObsidianService(), ...arguments);\n } catch (e) {\n if (\"code\" in e && typeof e.code != \"number\") {\n delete e.code;\n }\n throw e;\n }\n `, ...params);\n // TODO Should maybe add in the TransformReturn and TransformElement bit that wdio has recently added to the\n // execute types, though it causes weird affects if `func` returns type any.\n return result as Return;\n },\n\n /**\n * Executes an Obsidian command by id.\n * @param id Id of the command to run.\n */\n async executeObsidianCommand(this: WebdriverIO.Browser, id: string) {\n const result = await this.executeObsidian(\n ({app}, id) => (app as AppInternal).commands.executeCommandById(id),\n id,\n );\n if (!result) {\n throw Error(`Obsidian command ${id} not found or failed.`);\n }\n },\n\n /**\n * Returns the Obsidian app version this test is running under.\n */\n getObsidianVersion(this: WebdriverIO.Browser): string {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n return obsidianOptions.appVersion;\n },\n \n /**\n * Returns the Obsidian installer version this test is running under.\n */\n getObsidianInstallerVersion(this: WebdriverIO.Browser): string {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n return obsidianOptions.installerVersion;\n },\n\n /**\n * Returns the ObsidianPage object with convenience helper functions.\n * You can also just import the page object directly with\n * ```ts\n * import { obsidianPage } from \"wdio-obsidian-service\"\n * ```\n */\n getObsidianPage(this: WebdriverIO.Browser): ObsidianPage {\n return new ObsidianPage(this);\n },\n}\n\n/**\n * Extra commands added to the WDIO Browser instance.\n * \n * See also: https://webdriver.io/docs/api/browser\n * @interface\n * @category Utilities\n */\nexport type ObsidianBrowserCommands = typeof browserCommands & {\n /**\n * Relaunch obsidian. Can be used to switch to a new vault, change the plugin list, or just to reboot Obsidian.\n * \n * As this does a full reboot of Obsidian, this is rather slow. In many cases you can use\n * {@link ObsidianPage.resetVault} instead, which modifies vault files in place without rebooting Obsidian. If all\n * your tests use the same vault, you can also just set the vault in the `wdio.conf.mts` capabilities section.\n * \n * @param params.vault Path to the vault to open. The vault will be copied, so any changes made in your tests won't\n * be persited to the original. If omitted, it will reboot Obsidian with the current vault without creating a\n * new copy of the vault.\n * @param params.plugins List of plugin ids to enable. If omitted it will keep current plugin list. Note, all the\n * plugins must be defined in your wdio.conf.mts capabilities. You can also use the enablePlugin and \n * disablePlugin commands to change plugins without relaunching Obsidian.\n * @param params.theme Name of the theme to enable. If omitted it will keep the current theme. Pass \"default\" to\n * switch back to the default theme. Like with plugins, the theme must be defined in wdio.conf.mts.\n */\n reloadObsidian(params?: {\n vault?: string,\n plugins?: string[], theme?: string,\n }): Promise<void>;\n // This command is implemented in the service hooks.\n};\n\n/**\n * Argument passed to the {@link ObsidianBrowserCommands.executeObsidian | executeObsidian} browser command.\n * @category Types\n */\nexport interface ExecuteObsidianArg {\n /**\n * There is a global \"app\" instance, but that may be removed in the future so you can use this to access it from\n * tests. See https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines#Avoid+using+global+app+instance\n */\n app: obsidian.App,\n\n /**\n * The full obsidian API. See https://github.com/obsidianmd/obsidian-api/blob/master/obsidian.d.ts\n */\n obsidian: typeof obsidian,\n\n /**\n * Object containing all installed plugins mapped by their id. Plugin ids are converted to camelCase for ease of\n * destructuring.\n * \n * If you want to add typings for your plugin(s) you can use something like this in a `.d.ts`:\n * ```ts\n * import type MyPlugin from \"../src/main.js\"\n * declare module \"wdio-obsidian-service\" {\n * interface InstalledPlugins {\n * myPlugin: MyPlugin,\n * }\n * }\n * ```\n */\n plugins: InstalledPlugins,\n\n /**\n * The customized require function Obsidian makes available to plugins. This is also available globally, so you can\n * just use `require` directly instead of from {@link ExecuteObsidianArg} if you prefer.\n */\n require: NodeJS.Require,\n}\n\n/**\n * Installed plugins, mapped by their id converted to camelCase\n * @category Types\n */\nexport interface InstalledPlugins extends Record<string, obsidian.Plugin> {\n}\n","import { remote } from 'webdriverio'\nimport type { Capabilities, Options } from '@wdio/types'\nimport { ObsidianLauncherService, ObsidianWorkerService } from \"./service.js\"\nimport { ObsidianServiceOptions } from \"./types.js\"\n\n/**\n * Starts an Obsidian instance for WDIO standalone mode.\n * \n * For testing, you'll usually want to use the WDIO testrunner with Mocha, wdio.conf.mts, etc. to launch WDIO. However\n * if you want to use WDIO for some kind of scripting scenario, you can use this function to launch a WDIO standalone\n * session connected to Obsidian.\n * \n * See also: https://webdriver.io/docs/setuptypes/#standalone-mode\n * \n * Example:\n * ```ts\n * const browser = await startWdioSession({\n * capabilities: {\n * browserName: \"obsidian\",\n * browserVersion: \"latest\",\n * 'wdio:obsidianOptions': {\n * installerVersion: \"latest\",\n * vault: \"./test/vaults/basic\",\n * },\n * },\n * });\n * await browser.executeObsidian(({app}) => {\n * // extract some file metadata, edit the vault, etc...\n * });\n * await browser?.deleteSession();\n * ```\n * \n * @category WDIO Helpers\n */\nexport async function startWdioSession(\n params: Capabilities.WebdriverIOConfig,\n serviceOptions?: ObsidianServiceOptions,\n): Promise<WebdriverIO.Browser> {\n serviceOptions = serviceOptions ?? {};\n const capabilities = params.capabilities as WebdriverIO.Capabilities;\n const testRunnerOptions: Options.Testrunner = {\n cacheDir: params.cacheDir,\n };\n const launcherService = new ObsidianLauncherService(\n serviceOptions,\n [capabilities] as WebdriverIO.Capabilities,\n testRunnerOptions,\n );\n const workerService = new ObsidianWorkerService(serviceOptions, capabilities, testRunnerOptions);\n\n await launcherService.onPrepare(testRunnerOptions, [capabilities]);\n await workerService.beforeSession(testRunnerOptions, capabilities);\n\n const browser = await remote(params);\n\n await workerService.before(capabilities, [], browser);\n\n return browser;\n}\n"],"mappings":"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","names":["ObsidianLauncher","fsAsync","path","fileURLToPath","path","fsAsync","crypto","_","browser","path","browser","data","pluginId","themeName","path","workspacesPath","layout","file","strContent","binContent","parent","_","configDir","id","_","path","fileURLToPath","browser","androidVault","width","height","resolve","remote","fsAsync","browser","launcher","ObsidianLauncher"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wdio-obsidian-service",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
4
4
  "description": "A WebdriverIO service for end-to-end testing of Obsidian plugins",
5
5
  "keywords": [
6
6
  "obsidian",
@@ -55,11 +55,11 @@
55
55
  "tsup": "^8.5.0",
56
56
  "tsx": "^4.20.3",
57
57
  "typescript": "^5.9.2",
58
- "wdio-obsidian-reporter": "2.1.2"
58
+ "wdio-obsidian-reporter": "2.1.4"
59
59
  },
60
60
  "dependencies": {
61
61
  "lodash": "^4.17.21",
62
- "obsidian-launcher": "2.1.2",
62
+ "obsidian-launcher": "2.1.4",
63
63
  "semver": "^7.7.2",
64
64
  "tar": "^7.4.3"
65
65
  },