wdio-obsidian-service 2.0.0 → 2.0.2

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
@@ -1,4 +1,4 @@
1
- # WDIO Obsidian Service [![NPM](https://img.shields.io/npm/v/wdio-obsidian-service)](https://www.npmjs.com/package/wdio-obsidian-service)
1
+ # WDIO Obsidian Service [![](https://img.shields.io/npm/v/wdio-obsidian-service)](https://www.npmjs.com/package/wdio-obsidian-service)
2
2
 
3
3
  Test your [Obsidian](https://obsidian.md) plugins end-to-end using [WebdriverIO](https://webdriver.io)!
4
4
 
@@ -157,7 +157,7 @@ To set the app version use `browserVersion` or `'wdio:obsidianOptions'.appVersio
157
157
  - a specific version string like "1.7.7"
158
158
  - "latest": run the latest non-beta Obsidian version
159
159
  - "latest-beta": run the latest beta Obsidian version (or latest is there is no current beta)
160
- - To download Obsidian beta versions you'll need have an Obsidian Insiders account and either set the `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` env vars (`.env` is supported) or pre-download the Obsidian beta with `npx obsidian-launcher download -v latest-beta`.
160
+ - To download Obsidian beta versions you'll need to have an Obsidian Insiders account and either set the `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` env vars (`.env` file is supported) or pre-download the Obsidian beta with `npx obsidian-launcher download -v latest-beta`.
161
161
  - "earliest": run the `minAppVersion` set in your plugin's `manifest.json`
162
162
 
163
163
  To set the installer version use `'wdio:obsidianOptions'.installerVersion`. It can be set to one of:
@@ -195,7 +195,7 @@ it("test the thing", async function() {
195
195
 
196
196
  `wdio-obsidian-service` can test Obsidian desktop on Windows, MacOS, and Linux.
197
197
 
198
- There are two approaches to testing Obsidian mobile. You can emulate the mobile UI in desktop app, which is easy to set up but an imperfect emulation of mobile. Or you can run tests on the real mobile app using an Android Virtual Device (avd). `wdio-obsidian-service` supports both options.
198
+ `wdio-obsidian-service` supports two approaches to testing Obsidian mobile. You can emulate the mobile UI on the desktop app, which is easy to set up but an imperfect emulation of mobile. Or you can run tests on the real mobile app using an Android Virtual Device (avd).
199
199
 
200
200
  Testing on iOS is currently not supported.
201
201
 
@@ -231,13 +231,11 @@ To set this up, install Appium and the Appium Android driver:
231
231
  npm install --save-dev appium appium-uiautomator2-driver @wdio/appium-service
232
232
  ```
233
233
 
234
- Then follow these instructions: [Appium - Set up Android automation requirements](https://appium.io/docs/en/2.19/quickstart/uiauto2-driver/#set-up-android-automation-requirements)
234
+ Then follow [these instructions](https://appium.io/docs/en/latest/quickstart/uiauto2-driver/#set-up-android-automation-requirements) to install Android Studio. You can skip the `appium driver install ...` bit as you already installed the driver via npm above.
235
235
 
236
- You can skip the `appium driver install ...` bit as you already installed the driver via npm above.
236
+ Now you can use the Android Studio "Virtual Device Manager" to create a Android Virtual Device. Name the AVD `obsidian_test`.
237
237
 
238
- Then use the Android Studio "Virtual Device Manager" to create a Android Virtual Device and name it `obsidian_test`.
239
-
240
- Then set up a new `wdio.mobile.conf.mts` file like so:
238
+ Set up a new `wdio.mobile.conf.mts` file like this:
241
239
  ```ts
242
240
  import * as path from "path"
243
241
 
@@ -261,7 +259,12 @@ export const config: WebdriverIO.Config = {
261
259
  },
262
260
  }],
263
261
 
264
- services: ["obsidian"],
262
+ services: [
263
+ "obsidian",
264
+ ["appium", {
265
+ args: { allowInsecure: "chromedriver_autodownload,adb_shell" },
266
+ }],
267
+ ],
265
268
  reporters: ['obsidian'],
266
269
 
267
270
  cacheDir: path.resolve(".obsidian-cache"),
package/dist/index.d.ts CHANGED
@@ -43,7 +43,7 @@ declare class ObsidianPage extends BasePage {
43
43
  */
44
44
  getVaultPath(): string;
45
45
  /**
46
- * Return the the Obsidian config dir (just ".obsidian" unless you changed the config dir name in settings).
46
+ * Return the Obsidian config dir (just ".obsidian" unless you changed the config dir name in settings).
47
47
  */
48
48
  getConfigDir(): Promise<string>;
49
49
  /**
@@ -351,9 +351,9 @@ interface ObsidianCapabilityOptions {
351
351
  * Can be set to a specific version or one of:
352
352
  * - "latest": Run the latest non-beta Obsidian version
353
353
  * - "latest-beta": Run the latest beta Obsidian version (or latest is there is no current beta)
354
- * - To download Obsidian beta versions you'll need have an Obsidian Insiders account and either set the
355
- * `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` env vars (`.env` is supported) or pre-download the Obsidian beta
356
- * version with `npx obsidian-launcher download -v latest-beta`
354
+ * - To download Obsidian beta versions you'll need to have an Obsidian Insiders account and either set the
355
+ * `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` env vars (`.env` file is supported) or pre-download the Obsidian
356
+ * beta version with `npx obsidian-launcher download -v latest-beta`
357
357
  * - "earliest": Run the `minAppVersion` set in your `manifest.json`
358
358
  *
359
359
  * Defaults to "latest".
@@ -410,14 +410,15 @@ interface ObsidianCapabilityOptions {
410
410
  */
411
411
  vault?: string;
412
412
  /**
413
- * Set to true to simulate mobile on the Electron desktop app. This uses Obsidian `app.emulateMobile()` to switch
413
+ * Set to true to emulate mobile on the Electron desktop app. This uses Obsidian `app.emulateMobile()` to switch
414
414
  * Obsidian to the mobile UI, and you can use Chrome's mobileEmulation to set the screen size. You can compare
415
415
  * tablet vs phone UIs by setting the screen size or emulated device. Obsidian tablet UI triggers at
416
416
  * width/height >= 600.
417
417
  *
418
418
  * Note that Obsidian Mobile runs on Capacitor instead of Electron so there are various platform differences that
419
419
  * can't be emulated. But it's good enough for most cases as long as you aren't interacting directly with the
420
- * operating system or Electron APIs.
420
+ * operating system or Electron APIs. You can use an Android Virtual Device instead if you want a more accurate
421
+ * (but slower) mobile test.
421
422
  *
422
423
  * See [Mobile Emulation](../README.md#mobile-emulation) for more info.
423
424
  * See [Android](../README.md#android) if you want to test the real mobile app on an Android Virtual Device instead.
package/dist/index.js CHANGED
@@ -188,7 +188,7 @@ var ObsidianPage = class extends BasePage {
188
188
  return obsidianOptions.uploadedVault ?? obsidianOptions.vaultCopy;
189
189
  }
190
190
  /**
191
- * Return the the Obsidian config dir (just ".obsidian" unless you changed the config dir name in settings).
191
+ * Return the Obsidian config dir (just ".obsidian" unless you changed the config dir name in settings).
192
192
  */
193
193
  async getConfigDir() {
194
194
  return await this.browser.executeObsidian(({ app }) => app.vault.configDir);
@@ -369,7 +369,7 @@ var ObsidianPage = class extends BasePage {
369
369
  if (strContent2) {
370
370
  await app.vault.adapter.write(file2, strContent2);
371
371
  } else {
372
- const buffer = Uint8Array.from(atob(binContent2), (c) => c.charCodeAt(0));
372
+ const buffer = Uint8Array.from(atob(binContent2), (c) => c.charCodeAt(0)).buffer;
373
373
  await app.vault.adapter.writeBinary(file2, buffer);
374
374
  }
375
375
  }
@@ -502,7 +502,10 @@ var ObsidianPage = class extends BasePage {
502
502
  for (const [file, newFileInfo] of _2.sortBy([...newVault.entries()], 0)) {
503
503
  const currFileInfo = currVault.get(file);
504
504
  if (newFileInfo.type == "file") {
505
- const content = newFileInfo.sourceContent ?? await fsAsync2.readFile(newFileInfo.sourceFile);
505
+ let content = newFileInfo.sourceContent;
506
+ if (!content) {
507
+ content = (await fsAsync2.readFile(newFileInfo.sourceFile)).buffer;
508
+ }
506
509
  const hash = crypto2.createHash("SHA256").update(typeof content == "string" ? content : new Uint8Array(content)).digest("hex");
507
510
  if (!currFileInfo || currFileInfo.hash != hash) {
508
511
  await this.write(file, content);
@@ -902,6 +905,7 @@ var ObsidianWorkerService = class {
902
905
  const browser2 = this.browser;
903
906
  const obsidianOptions = browser2.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];
904
907
  if (obsidianOptions.emulateMobile && obsidianOptions.vault != void 0) {
908
+ await browser2.waitUntil(() => browser2.execute(() => !!window.electron));
905
909
  const [width, height] = await browser2.execute(() => [window.innerWidth, window.innerHeight]);
906
910
  await browser2.execute(async (width2, height2) => {
907
911
  await window.electron.remote.getCurrentWindow().setSize(width2, height2);
@@ -956,8 +960,10 @@ var ObsidianWorkerService = class {
956
960
  const remote2 = `${oldObsidianOptions.uploadedVault}/.obsidian`;
957
961
  const remoteCommunityPlugins = `${remote2}/community-plugins.json`;
958
962
  const remoteAppearance = `${remote2}/appearance.json`;
959
- await downloadFile(this, remoteCommunityPlugins, localCommunityPlugins);
960
- await downloadFile(this, remoteAppearance, localAppearance);
963
+ await downloadFile(this, remoteCommunityPlugins, localCommunityPlugins).catch(() => {
964
+ });
965
+ await downloadFile(this, remoteAppearance, localAppearance).catch(() => {
966
+ });
961
967
  await service.obsidianLauncher.setupVault({
962
968
  vault: oldObsidianOptions.vaultCopy,
963
969
  copy: false,
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/@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 { sleep, isAppium, uploadFolder, downloadFile, uploadFile, getAppiumOptions, fileExists } 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: any) => t.name != selection)) {\n throw Error(`Unknown theme: ${selection}`);\n }\n return currentThemes.map((t: any) => ({...t, enabled: selection != 'default' && t.name === selection}));\n } else {\n return currentThemes;\n }\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 } 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 any\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 = cap[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\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 = cap[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\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 * Opens the vault in appium.\n */\n private async appiumOpenVault() {\n const browser = this.browser!;\n const obsidianOptions: NormalizedObsidianCapabilityOptions = browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];\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 uploadFolder(browser, obsidianOptions.vaultCopy!, 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 * Sets appium app permissions and context\n */\n private async appiumSetContext() {\n const browser = this.browser!;\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\n await browser.execute(\"mobile: changePermissions\", {\n action: \"grant\",\n appPackage: \"md.obsidian\",\n permissions: \"all\",\n });\n\n await browser.execute(\"mobile: changePermissions\", {\n action: \"allow\",\n appPackage: \"md.obsidian\",\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 await browser.switchContext(\"WEBVIEW_md.obsidian\");\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: NormalizedObsidianCapabilityOptions = browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];\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 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: NormalizedObsidianCapabilityOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];\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 await sleep(2000)\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 downloadFile(this, remoteCommunityPlugins, localCommunityPlugins);\n await downloadFile(this, remoteAppearance, localAppearance);\n await service.obsidianLauncher.setupVault({\n vault: oldObsidianOptions.vaultCopy!, copy: false,\n plugins: selectedPlugins, themes: selectedThemes,\n });\n if (await fileExists(localCommunityPlugins)) {\n await uploadFile(this, localCommunityPlugins, remoteCommunityPlugins);\n }\n if (await fileExists(localAppearance)) {\n await uploadFile(this, localAppearance, remoteAppearance);\n }\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 sleep(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 * Handles vault and sandboxed config directory setup.\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 * Setup custom browser commands.\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.appiumSetContext();\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 async after(result: number, cap: WebdriverIO.Capabilities) {\n const browser = this.browser!;\n if (isAppium(cap)) {\n const packageName = await browser.getCurrentPackage();\n await browser.execute('mobile: terminateApp', { appId: packageName });\n // \"mobile: deleteFile\" doesn't work on folders\n // \"mobile:shell\" requires appium --allow-insecure adb_shell\n await browser.execute(\"mobile: shell\", {\n command: \"rm\", args: [\"-rf\", this.androidVaultDir],\n });\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 { OBSIDIAN_CAPABILITY_KEY, NormalizedObsidianCapabilityOptions } from \"../types.js\";\nimport { BasePage } from \"./basePage.js\";\nimport { isAppium } from \"../utils.js\";\nimport _ from \"lodash\";\n\n\n/** Returns true if a vault file path is hidden (either it or one of it's parent directories starts with \".\") */\nfunction isHidden(file: string) {\n return file.split(\"/\").some(p => p.startsWith(\".\"))\n}\n\n/** Returns true if this is a simple text file */\nfunction isText(file: string) {\n return [\".md\", \".json\", \".txt\", \".js\"].includes(path.extname(file).toLocaleLowerCase());\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 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 any).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 any).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 any).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));\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 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.metadataCache as any).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 const content = newFileInfo.sourceContent ?? await fsAsync.readFile(newFileInfo.sourceFile!);\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 is there is no current beta)\n * - To download Obsidian beta versions you'll need have an Obsidian Insiders account and either set the \n * `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` env vars (`.env` is supported) or pre-download the Obsidian beta\n * version with `npx obsidian-launcher download -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 simulate 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.\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 off 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 _ from \"lodash\";\n\nexport async function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\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: any = _.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 * Push a folder to the appium device.\n */\nexport async function uploadFolder(browser: WebdriverIO.Browser, src: string, dest: string) {\n src = path.resolve(src);\n dest = path.posix.normalize(dest).replace(/\\/$/, '');\n\n let files = await fsAsync.readdir(src, {recursive: true, withFileTypes: true});\n files = _.sortBy(files, f => path.join(f.parentPath, f.name)); // sort files before children\n\n await browser.execute(\"mobile: shell\", {command: \"mkdir\", args: [\"-p\", quote(dest)]});\n for (const file of files) {\n const srcPath = path.join(file.parentPath, file.name);\n const relPath = path.relative(src, path.join(file.parentPath, file.name));\n const destPath = path.posix.join(dest, relPath.split(path.sep).join(\"/\"));\n if (file.isDirectory()) {\n await browser.execute(\"mobile: shell\", {command: \"mkdir\", args: [\"-p\", quote(destPath)]});\n } else if (file.isFile()) {\n await uploadFile(browser, srcPath, destPath);\n }\n }\n}\n\n\n/**\n * Uploads a file to the appium device.\n * Wrapper around pushFile that works around a bug in pushFile where it doesn't escape special characters in parent\n * directory names. See https://github.com/appium/appium-android-driver/issues/1004\n */\nexport async function uploadFile(browser: WebdriverIO.Browser, src: string, dest: string) {\n src = path.resolve(src);\n dest = path.posix.normalize(dest);\n const slug = crypto.randomBytes(8).toString(\"base64url\").replace(/[-_]/g, '0');\n const tmpFile = `/data/local/tmp/upload-${slug}.tmp`;\n const content = await fsAsync.readFile(src);\n\n await browser.pushFile(tmpFile, content.toString('base64'));\n await browser.execute(\"mobile: shell\", {command: \"mv\", args: [tmpFile, quote(dest)]});\n}\n\n/**\n * Downloads a file from the appium device.\n */\nexport async function downloadFile(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","import type * as obsidian from \"obsidian\"\nimport { ObsidianPage } from \"./pageobjects/obsidianPage.js\"\nimport { NormalizedObsidianCapabilityOptions, OBSIDIAN_CAPABILITY_KEY } from \"./types.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(({app}, id) => (app as any).commands.executeCommandById(id), id);\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(serviceOptions, [capabilities] as any, testRunnerOptions);\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","resolve","path","browser","_","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/@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 { sleep, isAppium, uploadFolder, downloadFile, uploadFile, getAppiumOptions, fileExists } 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: any) => t.name != selection)) {\n throw Error(`Unknown theme: ${selection}`);\n }\n return currentThemes.map((t: any) => ({...t, enabled: selection != 'default' && t.name === selection}));\n } else {\n return currentThemes;\n }\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 } 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 any\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 = cap[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\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 = cap[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\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 * Opens the vault in appium.\n */\n private async appiumOpenVault() {\n const browser = this.browser!;\n const obsidianOptions: NormalizedObsidianCapabilityOptions = browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];\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 uploadFolder(browser, obsidianOptions.vaultCopy!, 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 * Sets appium app permissions and context\n */\n private async appiumSetContext() {\n const browser = this.browser!;\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\n await browser.execute(\"mobile: changePermissions\", {\n action: \"grant\",\n appPackage: \"md.obsidian\",\n permissions: \"all\",\n });\n\n await browser.execute(\"mobile: changePermissions\", {\n action: \"allow\",\n appPackage: \"md.obsidian\",\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 await browser.switchContext(\"WEBVIEW_md.obsidian\");\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: NormalizedObsidianCapabilityOptions = browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];\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: NormalizedObsidianCapabilityOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];\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 await sleep(2000)\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 downloadFile(this, remoteCommunityPlugins, localCommunityPlugins).catch(() => {});\n await downloadFile(this, remoteAppearance, localAppearance).catch(() => {});\n await service.obsidianLauncher.setupVault({\n vault: oldObsidianOptions.vaultCopy!, copy: false,\n plugins: selectedPlugins, themes: selectedThemes,\n });\n if (await fileExists(localCommunityPlugins)) {\n await uploadFile(this, localCommunityPlugins, remoteCommunityPlugins);\n }\n if (await fileExists(localAppearance)) {\n await uploadFile(this, localAppearance, remoteAppearance);\n }\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 sleep(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 * Handles vault and sandboxed config directory setup.\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 * Setup custom browser commands.\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.appiumSetContext();\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 async after(result: number, cap: WebdriverIO.Capabilities) {\n const browser = this.browser!;\n if (isAppium(cap)) {\n const packageName = await browser.getCurrentPackage();\n await browser.execute('mobile: terminateApp', { appId: packageName });\n // \"mobile: deleteFile\" doesn't work on folders\n // \"mobile:shell\" requires appium --allow-insecure adb_shell\n await browser.execute(\"mobile: shell\", {\n command: \"rm\", args: [\"-rf\", this.androidVaultDir],\n });\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 { OBSIDIAN_CAPABILITY_KEY, NormalizedObsidianCapabilityOptions } from \"../types.js\";\nimport { BasePage } from \"./basePage.js\";\nimport { isAppium } from \"../utils.js\";\nimport _ from \"lodash\";\n\n\n/** Returns true if a vault file path is hidden (either it or one of it's parent directories starts with \".\") */\nfunction isHidden(file: string) {\n return file.split(\"/\").some(p => p.startsWith(\".\"))\n}\n\n/** Returns true if this is a simple text file */\nfunction isText(file: string) {\n return [\".md\", \".json\", \".txt\", \".js\"].includes(path.extname(file).toLocaleLowerCase());\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 any).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 any).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 any).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 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.metadataCache as any).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 is 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 -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 off 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 _ from \"lodash\";\n\nexport async function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\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: any = _.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 * Push a folder to the appium device.\n */\nexport async function uploadFolder(browser: WebdriverIO.Browser, src: string, dest: string) {\n src = path.resolve(src);\n dest = path.posix.normalize(dest).replace(/\\/$/, '');\n\n let files = await fsAsync.readdir(src, {recursive: true, withFileTypes: true});\n files = _.sortBy(files, f => path.join(f.parentPath, f.name)); // sort files before children\n\n await browser.execute(\"mobile: shell\", {command: \"mkdir\", args: [\"-p\", quote(dest)]});\n for (const file of files) {\n const srcPath = path.join(file.parentPath, file.name);\n const relPath = path.relative(src, path.join(file.parentPath, file.name));\n const destPath = path.posix.join(dest, relPath.split(path.sep).join(\"/\"));\n if (file.isDirectory()) {\n await browser.execute(\"mobile: shell\", {command: \"mkdir\", args: [\"-p\", quote(destPath)]});\n } else if (file.isFile()) {\n await uploadFile(browser, srcPath, destPath);\n }\n }\n}\n\n\n/**\n * Uploads a file to the appium device.\n * Wrapper around pushFile that works around a bug in pushFile where it doesn't escape special characters in parent\n * directory names. See https://github.com/appium/appium-android-driver/issues/1004\n */\nexport async function uploadFile(browser: WebdriverIO.Browser, src: string, dest: string) {\n src = path.resolve(src);\n dest = path.posix.normalize(dest);\n const slug = crypto.randomBytes(8).toString(\"base64url\").replace(/[-_]/g, '0');\n const tmpFile = `/data/local/tmp/upload-${slug}.tmp`;\n const content = await fsAsync.readFile(src);\n\n await browser.pushFile(tmpFile, content.toString('base64'));\n await browser.execute(\"mobile: shell\", {command: \"mv\", args: [tmpFile, quote(dest)]});\n}\n\n/**\n * Downloads a file from the appium device.\n */\nexport async function downloadFile(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","import type * as obsidian from \"obsidian\"\nimport { ObsidianPage } from \"./pageobjects/obsidianPage.js\"\nimport { NormalizedObsidianCapabilityOptions, OBSIDIAN_CAPABILITY_KEY } from \"./types.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(({app}, id) => (app as any).commands.executeCommandById(id), id);\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(serviceOptions, [capabilities] as any, testRunnerOptions);\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","resolve","path","browser","_","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.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "A WebdriverIO service for end-to-end testing of Obsidian plugins",
5
5
  "keywords": [
6
6
  "obsidian",
@@ -44,9 +44,11 @@
44
44
  "@types/chai": "^5.2.2",
45
45
  "@types/lodash": "^4.17.20",
46
46
  "@types/mocha": "^10.0.10",
47
- "@types/node": "^18.19.121",
47
+ "@types/node": "^18.15.3",
48
+ "@types/semver": "^7.7.0",
48
49
  "@wdio/appium-service": "^9.18.4",
49
50
  "@wdio/cli": "^9.18.4",
51
+ "@wdio/globals": "^9.17.0",
50
52
  "@wdio/local-runner": "^9.18.4",
51
53
  "@wdio/mocha-framework": "^9.18.0",
52
54
  "@wdio/spec-reporter": "^9.18.0",
@@ -58,19 +60,19 @@
58
60
  "ts-node": "^10.9.2",
59
61
  "tsup": "^8.5.0",
60
62
  "tsx": "^4.20.3",
61
- "typescript": "^5.8.3",
62
- "wdio-obsidian-reporter": "^2.0.0"
63
+ "typescript": "^5.9.2",
64
+ "wdio-obsidian-reporter": "2.0.2"
63
65
  },
64
66
  "dependencies": {
65
67
  "lodash": "^4.17.21",
66
- "obsidian-launcher": "^2.0.0",
68
+ "obsidian-launcher": "2.0.2",
67
69
  "semver": "^7.7.2"
68
70
  },
69
71
  "peerDependencies": {
70
72
  "@wdio/cli": "^9.18.1",
71
73
  "@wdio/local-runner": "^9.18.1",
72
74
  "@wdio/logger": "^9.18.0",
73
- "@wdio/mocha-framework": "^9.18.1",
75
+ "@wdio/mocha-framework": "^9.18.0",
74
76
  "obsidian": "^1.8.7",
75
77
  "webdriverio": "^9.18.1"
76
78
  },