wdio-obsidian-service 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -262,7 +262,8 @@ export const config: WebdriverIO.Config = {
262
262
  platformName: 'Android',
263
263
  'appium:automationName': 'UiAutomator2',
264
264
  'appium:avd': "obsidian_test",
265
- 'appium:noReset': true, // wdio-obsidian-service will handle installing Obsidian
265
+ // set for faster tests, the service will reset Obsidian when needed
266
+ 'appium:noReset': true,
266
267
  'wdio:obsidianOptions': {
267
268
  plugins: ["."],
268
269
  vault: "test/vaults/simple",
package/dist/index.d.ts CHANGED
@@ -42,6 +42,16 @@ declare class ObsidianPage extends BasePage {
42
42
  * wdio-obsidian-service copies your vault before running tests, so this is the path to the temporary copy.
43
43
  */
44
44
  getVaultPath(): string;
45
+ /**
46
+ * Run an Obsidian CLI command.
47
+ *
48
+ * The Obsidian CLI Only works on Obsidian >=1.12.0 with installer >=1.11.7
49
+ * See https://help.obsidian.md/cli
50
+ */
51
+ runObsidianCli(args: string[]): Promise<{
52
+ stdout: string;
53
+ stderr: string;
54
+ }>;
45
55
  /**
46
56
  * Return the Obsidian config dir (just ".obsidian" unless you changed the config dir name in settings).
47
57
  */
package/dist/index.js CHANGED
@@ -15,13 +15,16 @@ import ObsidianLauncher from "obsidian-launcher";
15
15
  import * as path2 from "path";
16
16
  import * as fsAsync2 from "fs/promises";
17
17
  import * as crypto2 from "crypto";
18
+ import { promisify } from "util";
19
+ import child_process from "child_process";
20
+ import * as semver from "semver";
18
21
  import { fileURLToPath } from "url";
19
22
  import _2 from "lodash";
20
23
 
21
24
  // src/types.ts
22
25
  var OBSIDIAN_CAPABILITY_KEY = "wdio:obsidianOptions";
23
26
 
24
- // ../../node_modules/.pnpm/@wdio+globals@9.17.0_expect-webdriverio@5.6.3_webdriverio@9.18.1/node_modules/@wdio/globals/build/index.js
27
+ // ../../node_modules/.pnpm/@wdio+globals@9.17.0_expect-webdriverio@5.6.4_webdriverio@9.18.1/node_modules/@wdio/globals/build/index.js
25
28
  var globals = globalThis._wdioGlobals = globalThis._wdioGlobals || /* @__PURE__ */ new Map();
26
29
  var 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.`;
27
30
  function proxyHandler(key) {
@@ -171,6 +174,13 @@ async function appiumDownloadFile(browser2, src, dest) {
171
174
  const content = Buffer.from(await browser2.pullFile(src), "base64");
172
175
  await fsAsync.writeFile(dest, content);
173
176
  }
177
+ async function navigateAndWait(browser2, func) {
178
+ await browser2.execute(() => {
179
+ window._waitingForNavigation = true;
180
+ });
181
+ await browser2.execute(func);
182
+ await browser2.waitUntil(() => browser2.execute(() => !("_waitingForNavigation" in window)));
183
+ }
174
184
  function normalizePath(p) {
175
185
  p = p.replace(/([\\/])+/g, "/").replace(/(^\/+|\/+$)/g, "");
176
186
  if (p == "") {
@@ -188,6 +198,7 @@ function isText(file) {
188
198
  }
189
199
 
190
200
  // src/pageobjects/obsidianPage.ts
201
+ var execFile = promisify(child_process.execFile);
191
202
  var ObsidianPage = class extends BasePage {
192
203
  getObsidianCapabilities() {
193
204
  return this.browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];
@@ -204,6 +215,24 @@ var ObsidianPage = class extends BasePage {
204
215
  }
205
216
  return obsidianOptions.uploadedVault ?? obsidianOptions.vaultCopy;
206
217
  }
218
+ /**
219
+ * Run an Obsidian CLI command.
220
+ *
221
+ * The Obsidian CLI Only works on Obsidian >=1.12.0 with installer >=1.11.7
222
+ * See https://help.obsidian.md/cli
223
+ */
224
+ async runObsidianCli(args) {
225
+ const { appVersion, installerVersion, binaryPath } = this.getObsidianCapabilities();
226
+ if (semver.lt(appVersion, "1.12.0") || semver.lt(installerVersion, "1.11.7")) {
227
+ throw Error(`Obsidian CLI only works on app >=1.12.0 and installer >=1.11.7`);
228
+ }
229
+ if (isAppium(this.browser.requestedCapabilities)) {
230
+ throw Error(`Obsidian CLI only works on desktop`);
231
+ }
232
+ const chromeArgs = this.browser.requestedCapabilities["goog:chromeOptions"].args;
233
+ const cliBinary = binaryPath.replace(/.exe$/, ".com");
234
+ return await execFile(cliBinary, [...args, ...chromeArgs]);
235
+ }
207
236
  /**
208
237
  * Return the Obsidian config dir (just ".obsidian" unless you changed the config dir name in settings).
209
238
  */
@@ -216,13 +245,26 @@ var ObsidianPage = class extends BasePage {
216
245
  */
217
246
  async getPlatform() {
218
247
  const obsidianOptions = this.getObsidianCapabilities();
248
+ const appium = isAppium(this.browser.requestedCapabilities);
249
+ const emulateMobile = obsidianOptions.emulateMobile;
250
+ const platform = {
251
+ isDesktop: !(appium || emulateMobile),
252
+ isMobile: appium || emulateMobile,
253
+ isDesktopApp: !appium,
254
+ isMobileApp: appium,
255
+ isIosApp: false,
256
+ // iOS is not supported
257
+ isAndroidApp: appium,
258
+ isMacOS: !appium && process.platform == "darwin",
259
+ isWin: !appium && process.platform == "win32",
260
+ isLinux: appium || process.platform == "linux",
261
+ isSafari: false
262
+ // iOS is not supported
263
+ };
219
264
  if (obsidianOptions.vault !== void 0) {
220
- return await this.browser.executeObsidian(({ obsidian }) => {
265
+ const obsidianPlatform = await this.browser.executeObsidian(({ obsidian }) => {
221
266
  const p = obsidian.Platform;
222
267
  return {
223
- isDesktop: p.isDesktop,
224
- isMobile: p.isMobile,
225
- isDesktopApp: p.isDesktopApp,
226
268
  isMobileApp: p.isMobileApp,
227
269
  isIosApp: p.isIosApp,
228
270
  isAndroidApp: p.isAndroidApp,
@@ -234,9 +276,8 @@ var ObsidianPage = class extends BasePage {
234
276
  isSafari: p.isSafari
235
277
  };
236
278
  });
279
+ Object.assign(platform, _2.pickBy(obsidianPlatform, (x) => x != null));
237
280
  } else {
238
- const appium = isAppium(this.browser.requestedCapabilities);
239
- const emulateMobile = obsidianOptions.emulateMobile;
240
281
  const [width, height] = await this.browser.execute(() => [window.innerWidth, window.innerHeight]);
241
282
  let isTablet = false;
242
283
  let isPhone = false;
@@ -244,23 +285,9 @@ var ObsidianPage = class extends BasePage {
244
285
  isTablet = width >= 600 && height >= 600;
245
286
  isPhone = !isTablet;
246
287
  }
247
- return {
248
- isDesktop: !(appium || emulateMobile),
249
- isMobile: appium || emulateMobile,
250
- isDesktopApp: !appium,
251
- isMobileApp: appium,
252
- isIosApp: false,
253
- // iOS is not supported
254
- isAndroidApp: appium,
255
- isPhone,
256
- isTablet,
257
- isMacOS: !appium && process.platform == "darwin",
258
- isWin: !appium && process.platform == "win32",
259
- isLinux: !appium && process.platform == "linux",
260
- isSafari: false
261
- // iOS is not supported
262
- };
288
+ Object.assign(platform, { isTablet, isPhone });
263
289
  }
290
+ return platform;
264
291
  }
265
292
  /**
266
293
  * Enables a plugin by ID
@@ -667,7 +694,7 @@ var browserCommands = {
667
694
  };
668
695
 
669
696
  // src/service.ts
670
- import semver from "semver";
697
+ import semver2 from "semver";
671
698
  import _3 from "lodash";
672
699
  var log = logger("wdio-obsidian-service");
673
700
  function getDefaultCacheDir(rootDir) {
@@ -760,7 +787,7 @@ var ObsidianLauncherService = class {
760
787
  );
761
788
  let appVersion = cap.browserVersion ?? cap[OBSIDIAN_CAPABILITY_KEY]?.appVersion ?? "latest";
762
789
  appVersion = (await this.obsidianLauncher.getVersionInfo(appVersion)).version;
763
- if (semver.lt(appVersion, minSupportedObsidianVersion)) {
790
+ if (semver2.lt(appVersion, minSupportedObsidianVersion)) {
764
791
  throw Error(`Minimum supported Obsidian version is ${minSupportedObsidianVersion}`);
765
792
  }
766
793
  if (isAppium(cap)) {
@@ -960,7 +987,10 @@ var ObsidianWorkerService = class {
960
987
  // requires appium --allow-insecure adb_shell
961
988
  });
962
989
  const context = "WEBVIEW_md.obsidian";
963
- await browser2.waitUntil(async () => (await browser2.getContexts()).includes(context));
990
+ await browser2.waitUntil(
991
+ async () => (await browser2.getContexts()).includes(context),
992
+ { timeout: 3e4, interval: 100 }
993
+ );
964
994
  await browser2.switchContext(context);
965
995
  await browser2.execute(() => {
966
996
  if (window.location.href != "http://localhost/") {
@@ -985,20 +1015,18 @@ var ObsidianWorkerService = class {
985
1015
  localStorage.setItem("mobile-external-vaults", JSON.stringify([androidVault2]));
986
1016
  localStorage.setItem("mobile-selected-vault", androidVault2);
987
1017
  localStorage.setItem(`enable-plugin-${androidVault2}`, "true");
988
- window.location.reload();
989
1018
  }, androidVault);
1019
+ await navigateAndWait(browser2, () => location.reload());
990
1020
  }
991
1021
  /**
992
1022
  * Close any open vault and go back to the vault switcher
993
1023
  */
994
1024
  async appiumCloseVault() {
995
1025
  const browser2 = this.browser;
996
- await browser2.execute(async () => {
997
- if (localStorage.length > 0) {
998
- localStorage.clear();
999
- location.reload();
1000
- }
1001
- });
1026
+ if (await browser2.execute(() => localStorage.length > 0)) {
1027
+ await browser2.execute(() => localStorage.clear());
1028
+ await navigateAndWait(browser2, () => location.reload());
1029
+ }
1002
1030
  }
1003
1031
  /**
1004
1032
  * Waits for Obsidian to be ready, and does some other final setup.
@@ -1007,7 +1035,10 @@ var ObsidianWorkerService = class {
1007
1035
  const browser2 = this.browser;
1008
1036
  const obsidianOptions = getNormalizedObsidianOptions(browser2.requestedCapabilities);
1009
1037
  if (obsidianOptions.emulateMobile && obsidianOptions.vault != void 0) {
1010
- await browser2.waitUntil(() => browser2.execute(() => !!window.electron));
1038
+ await browser2.waitUntil(
1039
+ () => browser2.execute(() => !!window.electron),
1040
+ { timeout: 1e4, interval: 100 }
1041
+ );
1011
1042
  const [width, height] = await browser2.execute(() => [window.innerWidth, window.innerHeight]);
1012
1043
  await browser2.execute(async (width2, height2) => {
1013
1044
  await window.electron.remote.getCurrentWindow().setSize(width2, height2);
@@ -1017,7 +1048,7 @@ var ObsidianWorkerService = class {
1017
1048
  await browser2.waitUntil(
1018
1049
  // wait until the helper plugin is loaded
1019
1050
  () => browser2.execute(() => !!window.wdioObsidianService),
1020
- { timeout: 30 * 1e3, interval: 100 }
1051
+ { timeout: 6e4, interval: 100 }
1021
1052
  );
1022
1053
  await browser2.executeObsidian(async ({ app }) => {
1023
1054
  await new Promise((resolve2) => app.workspace.onLayoutReady(resolve2));
@@ -1052,9 +1083,7 @@ var ObsidianWorkerService = class {
1052
1083
  await service.setupVault(this.requestedCapabilities);
1053
1084
  await service.appiumOpenVault();
1054
1085
  } else {
1055
- await this.execute(() => {
1056
- window.location.replace("http://localhost/_capacitor_file_/not-a-file");
1057
- });
1086
+ await navigateAndWait(this, () => location.replace("http://localhost/_capacitor_file_/not-a-file"));
1058
1087
  const local = path3.join(oldObsidianOptions.vaultCopy, ".obsidian");
1059
1088
  const localCommunityPlugins = path3.join(local, "community-plugins.json");
1060
1089
  const localAppearance = path3.join(local, "appearance.json");
@@ -1074,9 +1103,7 @@ var ObsidianWorkerService = class {
1074
1103
  let files = [localCommunityPlugins, localAppearance];
1075
1104
  files = (await Promise.all(files.map(async (f) => await fileExists(f) ? f : ""))).filter((f) => f);
1076
1105
  await appiumUploadFiles(this, { src: local, dest: remote2, files });
1077
- await this.execute(() => {
1078
- window.location.replace("http://localhost/");
1079
- });
1106
+ await navigateAndWait(this, () => location.replace("http://localhost/"));
1080
1107
  }
1081
1108
  } else {
1082
1109
  const newCap = _3.cloneDeep(
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/service.ts","../src/pageobjects/obsidianPage.ts","../src/types.ts","../../../node_modules/.pnpm/@wdio+globals@9.17.0_expect-webdriverio@5.6.3_webdriverio@9.18.1/node_modules/@wdio/globals/build/index.js","../src/pageobjects/basePage.ts","../src/utils.ts","../src/browserCommands.ts","../src/standalone.ts"],"sourcesContent":["/**\n * @module\n * @document ../README.md\n * @categoryDescription Options\n * Capability and service options.\n * @categoryDescription WDIO Helpers\n * Helpers for use in wdio.conf.mts, or for launching WDIO in standalone mode.\n * @categoryDescription Utilities\n * Browser commands and helper functions for writing tests.\n */\nimport ObsidianLauncher from \"obsidian-launcher\";\nimport { deprecate } from \"util\";\n\nimport { ObsidianLauncherService, ObsidianWorkerService } from \"./service.js\";\n/** @hidden */\nexport default ObsidianWorkerService;\n/** @hidden */\nexport const launcher = ObsidianLauncherService;\n\nexport type { ObsidianCapabilityOptions, ObsidianServiceOptions } from \"./types.js\";\nexport type { ObsidianBrowserCommands, ExecuteObsidianArg, InstalledPlugins } from \"./browserCommands.js\";\nexport { default as obsidianPage } from \"./pageobjects/obsidianPage.js\";\nexport type { ObsidianPage, Platform } from \"./pageobjects/obsidianPage.js\";\nexport type { PluginEntry, ThemeEntry } from \"obsidian-launcher\";\n\nexport { minSupportedObsidianVersion } from \"./service.js\";\nexport { startWdioSession } from \"./standalone.js\";\n\n// Some convenience helpers for use in wdio.conf.mts\n\n/**\n * Returns true if there is a current Obsidian beta and we have the credentials to download it, or its already in cache.\n * @category WDIO Helpers\n * @param opts.cacheDir Obsidian cache dir, defaults to `.obsidian-cache`.\n */\nexport async function obsidianBetaAvailable(opts: string|{cacheDir?: string} = {}) {\n opts = typeof opts == \"string\" ? {cacheDir: opts} : opts;\n const launcher = new ObsidianLauncher(opts);\n const versionInfo = await launcher.getVersionInfo(\"latest-beta\");\n return versionInfo.isBeta && await launcher.isAvailable(versionInfo.version);\n}\n\n/**\n * Resolves Obsidian app and installer version strings to absolute versions.\n * \n * @category WDIO Helpers\n * @deprecated Use parseObsidianVersions instead\n */\nexport const resolveObsidianVersions = deprecate(async function(\n appVersion: string, installerVersion: string, cacheDir?: string,\n): Promise<[string, string]> {\n const launcher = new ObsidianLauncher({cacheDir: cacheDir});\n return await launcher.resolveVersion(appVersion, installerVersion);\n}, 'resolveObsidianVersions is deprecated, use parseObsidianVersions instead');\n\n\n/**\n * Parses a string of Obsidian versions into [appVersion, installerVersion] tuples. This is a convenience helper for use\n * in `wdio.conf.mts`\n * \n * `versions` should be a space separated list of Obsidian app versions. You can optionally specify the installer\n * version by using \"appVersion/installerVersion\" e.g. `\"1.7.7/1.8.10\"`.\n * \n * Example: \n * ```js\n * parseObsidianVersions(\"1.8.10/1.7.7 latest latest-beta/earliest\")\n * ```\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n * \n * @category WDIO Helpers\n * @param versions string to parse\n * @param opts.cacheDir Obsidian cache dir, defaults to `.obsidian-cache`.\n * @returns [appVersion, installerVersion][] resolved to specific versions.\n */\nexport async function parseObsidianVersions(\n versions: string,\n opts: {cacheDir?: string} = {},\n): Promise<[string, string][]> {\n const launcher = new ObsidianLauncher({cacheDir: opts.cacheDir});\n return launcher.parseVersions(versions);\n}\n","import fs from \"fs\"\nimport fsAsync from \"fs/promises\"\nimport path from \"path\"\nimport { SevereServiceError } from 'webdriverio'\nimport type { Capabilities, Options, Services } from '@wdio/types'\nimport logger from '@wdio/logger'\nimport { fileURLToPath } from \"url\"\nimport ObsidianLauncher, {\n PluginEntry, ThemeEntry, DownloadedPluginEntry, DownloadedThemeEntry,\n} from \"obsidian-launcher\"\nimport { browserCommands } from \"./browserCommands.js\"\nimport {\n ObsidianServiceOptions, NormalizedObsidianCapabilityOptions, OBSIDIAN_CAPABILITY_KEY,\n} from \"./types.js\"\nimport {\n isAppium, appiumUploadFiles, appiumDownloadFile, getAppiumOptions, fileExists,\n} from \"./utils.js\";\nimport semver from \"semver\"\nimport _ from \"lodash\"\n\n\nconst log = logger(\"wdio-obsidian-service\");\n\nfunction getDefaultCacheDir(rootDir: string) {\n return path.resolve(rootDir, process.env.WEBDRIVER_CACHE_DIR ?? process.env.OBSIDIAN_CACHE ?? \"./.obsidian-cache\");\n}\n\n/** By default wdio continues on service errors, so we throw a SevereServiceError to make it bail on error */\nfunction getServiceErrorMessage(e: any) {\n return (\n `Failed to download and setup Obsidian. Caused by:\\n` +\n `${e.stack}\\n`+\n ` ------The above causes:-----`\n );\n}\n\nfunction resolveEntry(rootDir: string, entry: PluginEntry|ThemeEntry): PluginEntry|ThemeEntry {\n if (typeof entry == \"string\") {\n return path.resolve(rootDir, entry);\n } else if ('path' in entry) {\n return {...entry, path: path.resolve(rootDir, entry.path)};\n } else {\n return entry;\n }\n}\n\n/** Returns a plugin list with only the selected plugin ids enabled. */\nfunction selectPlugins(currentPlugins: DownloadedPluginEntry[], selection?: string[]): DownloadedPluginEntry[] {\n if (selection !== undefined) {\n const unknownPlugins = _.difference(selection, currentPlugins.map(p => p.id));\n if (unknownPlugins.length > 0) {\n throw Error(`Unknown plugin ids: ${unknownPlugins.join(', ')}`)\n }\n return currentPlugins.map(p => ({\n ...p,\n enabled: selection.includes(p.id) || p.id == \"wdio-obsidian-service-plugin\",\n }));\n } else {\n return currentPlugins;\n }\n}\n\n/** Returns a theme list with only the selected theme enabled. */\nfunction selectThemes(currentThemes: DownloadedThemeEntry[], selection?: string): DownloadedThemeEntry[] {\n if (selection !== undefined) {\n if (selection != \"default\" && currentThemes.every(t => t.name != selection)) {\n throw Error(`Unknown theme: ${selection}`);\n }\n return currentThemes.map(t => ({...t, enabled: selection != 'default' && t.name === selection}));\n } else {\n return currentThemes;\n }\n}\n\nfunction getNormalizedObsidianOptions(cap: WebdriverIO.Capabilities): NormalizedObsidianCapabilityOptions {\n return cap[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n}\n\n\n/**\n * Minimum Obsidian version that wdio-obsidian-service supports.\n * @category WDIO Helpers\n */\nexport const minSupportedObsidianVersion: string = \"1.0.3\"\n\n\n/**\n * wdio launcher service.\n * Use in wdio.conf.mts like so:\n * ```ts\n * services: ['obsidian'],\n * ```\n * @hidden\n */\nexport class ObsidianLauncherService implements Services.ServiceInstance {\n private obsidianLauncher: ObsidianLauncher\n private readonly helperPluginPath: string\n private readonly rootDir: string\n\n constructor (\n public options: ObsidianServiceOptions,\n public capabilities: WebdriverIO.Capabilities,\n public config: Options.Testrunner\n ) {\n this.rootDir = config.rootDir || process.cwd();\n this.obsidianLauncher = new ObsidianLauncher({\n cacheDir: config.cacheDir ?? getDefaultCacheDir(this.rootDir),\n versionsUrl: options.versionsUrl,\n communityPluginsUrl: options.communityPluginsUrl, communityThemesUrl: options.communityThemesUrl,\n });\n this.helperPluginPath = path.resolve(fileURLToPath(import.meta.url), '../../helper-plugin');\n }\n\n /**\n * Validates wdio:obsidianOptions and downloads Obsidian, plugins, and themes.\n */\n async onPrepare(config: Options.Testrunner, capabilities: Capabilities.TestrunnerCapabilities) {\n try {\n if (!Array.isArray(capabilities)) {\n capabilities = Object.values(capabilities).map(\n (multiremoteOption) => (multiremoteOption as Capabilities.WithRequestedCapabilities).capabilities,\n );\n }\n\n const obsidianCapabilities = capabilities.flatMap((cap) => {\n if ((\"browserName\" in cap) && cap.browserName === \"obsidian\") {\n return [cap as WebdriverIO.Capabilities];\n } else {\n return [];\n }\n });\n\n for (const cap of obsidianCapabilities) {\n const obsidianOptions = cap[OBSIDIAN_CAPABILITY_KEY] ?? {};\n \n // check vault\n const vault = obsidianOptions.vault ? path.resolve(this.rootDir, obsidianOptions.vault) : undefined;\n if (vault && !fs.existsSync(vault)) {\n throw Error(`Vault \"${vault}\" doesn't exist`)\n }\n \n // download plugins and themes to cache\n const plugins = await this.obsidianLauncher.downloadPlugins(\n (obsidianOptions.plugins ?? [])\n .concat([this.helperPluginPath]) // Always install the helper plugin\n .map(p => resolveEntry(this.rootDir, p) as PluginEntry)\n );\n\n const themes = await this.obsidianLauncher.downloadThemes(\n (obsidianOptions.themes ?? [])\n .map(t => resolveEntry(this.rootDir, t) as ThemeEntry),\n );\n\n let appVersion = cap.browserVersion ?? cap[OBSIDIAN_CAPABILITY_KEY]?.appVersion ?? \"latest\";\n appVersion = (await this.obsidianLauncher.getVersionInfo(appVersion)).version;\n if (semver.lt(appVersion, minSupportedObsidianVersion)) {\n throw Error(`Minimum supported Obsidian version is ${minSupportedObsidianVersion}`)\n }\n\n if (isAppium(cap)) {\n let apk = getAppiumOptions(cap).app;\n if (!apk) {\n apk = await this.obsidianLauncher.downloadAndroid(appVersion);\n }\n let chromedriverDir = getAppiumOptions(cap).chromedriverExecutableDir;\n if (!chromedriverDir) {\n chromedriverDir = path.join(this.obsidianLauncher.cacheDir, 'appium-chromedriver');\n }\n\n const normalizedObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...obsidianOptions,\n plugins, themes, vault: vault,\n appVersion, installerVersion: appVersion,\n emulateMobile: false,\n }\n cap[OBSIDIAN_CAPABILITY_KEY] = normalizedObsidianOptions;\n cap['appium:app'] = apk;\n cap['appium:chromedriverExecutableDir'] = chromedriverDir;\n cap[\"wdio:enforceWebDriverClassic\"] = true; // BiDi doesn't seem to work on Obsidian mobile\n // appium-service expects these to not be set\n delete cap.browserName;\n delete cap.browserVersion;\n if (!getAppiumOptions(cap)['noReset']) {\n console.warn(\"Note: For best performance set noReset to true, wdio-obsidian-service will handle resetting Obsidian between tests.\")\n }\n } else {\n const [, installerVersion] = await this.obsidianLauncher.resolveVersion(\n appVersion, obsidianOptions.installerVersion ?? \"earliest\",\n );\n const installerInfo = await this.obsidianLauncher.getInstallerInfo(installerVersion);\n\n let installerPath: string;\n if (obsidianOptions.binaryPath) {\n installerPath = path.resolve(this.rootDir, obsidianOptions.binaryPath)\n } else {\n installerPath = await this.obsidianLauncher.downloadInstaller(installerVersion);\n }\n let appPath: string;\n if (obsidianOptions.appPath) {\n appPath = path.resolve(this.rootDir, obsidianOptions.appPath)\n } else {\n appPath = await this.obsidianLauncher.downloadApp(appVersion);\n }\n let chromedriverPath = cap['wdio:chromedriverOptions']?.binary;\n // wdio can't download chromedriver for versions less than 115 automatically. Fetching it ourselves is\n // also a bit faster as it skips the chromedriver version detection step.\n if (!chromedriverPath) {\n chromedriverPath = await this.obsidianLauncher.downloadChromedriver(installerVersion);\n }\n\n const normalizedObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...obsidianOptions,\n plugins, themes, vault,\n binaryPath: installerPath, appPath: appPath,\n appVersion, installerVersion,\n emulateMobile: obsidianOptions.emulateMobile ?? false,\n }\n\n cap.browserName = \"chrome\";\n cap.browserVersion = installerInfo.chrome;\n cap[OBSIDIAN_CAPABILITY_KEY] = normalizedObsidianOptions;\n cap['goog:chromeOptions'] = {\n binary: installerPath,\n windowTypes: [\"app\", \"webview\"],\n ...cap['goog:chromeOptions'],\n args: [\n // Workaround for SUID issue on linux. See https://github.com/electron/electron/issues/42510\n ...(process.platform == 'linux' ? [\"--no-sandbox\"] : []),\n ...(cap['goog:chromeOptions']?.args ?? [])\n ],\n }\n cap['wdio:chromedriverOptions'] = {\n // allowedIps is not included in the types, but gets passed as --allowed-ips to chromedriver.\n // It defaults to [\"0.0.0.0\"] which makes Windows Firewall complain, and we don't need remote\n // connections anyways.\n allowedIps: [],\n ...cap['wdio:chromedriverOptions'],\n binary: chromedriverPath,\n } as WebdriverIO.ChromedriverOptions;\n cap[\"wdio:enforceWebDriverClassic\"] = true; // electron doesn't support BiDi yet.\n }\n }\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n}\n\n\n/**\n * wdio worker service.\n * Use in wdio.conf.mts like so:\n * ```ts\n * services: ['obsidian'],\n * ```\n * @hidden\n */\nexport class ObsidianWorkerService implements Services.ServiceInstance {\n private obsidianLauncher: ObsidianLauncher\n private browser: WebdriverIO.Browser|undefined;\n /** Directories to clean up after the tests */\n private tmpDirs: string[]\n /** Path on Android devices to store temporary vaults */\n private androidVaultDir = \"/storage/emulated/0/Documents/wdio-obsidian-service-vaults\";\n\n constructor (\n public options: ObsidianServiceOptions,\n public capabilities: WebdriverIO.Capabilities,\n public config: Options.Testrunner\n ) {\n this.obsidianLauncher = new ObsidianLauncher({\n cacheDir: config.cacheDir ?? getDefaultCacheDir(config.rootDir || process.cwd()),\n versionsUrl: options.versionsUrl,\n communityPluginsUrl: options.communityPluginsUrl, communityThemesUrl: options.communityThemesUrl,\n });\n this.tmpDirs = [];\n }\n\n /**\n * Creates a copy of the vault with plugins and themes installed\n */\n private async setupVault(cap: WebdriverIO.Capabilities) {\n const obsidianOptions = getNormalizedObsidianOptions(cap);\n let vaultCopy: string|undefined;\n if (obsidianOptions.vault != undefined) {\n log.info(`Opening vault ${obsidianOptions.vault}`);\n vaultCopy = await this.obsidianLauncher.setupVault({\n vault: obsidianOptions.vault,\n copy: true,\n plugins: obsidianOptions.plugins,\n themes: obsidianOptions.themes,\n });\n this.tmpDirs.push(vaultCopy);\n } else {\n log.info(`Opening Obsidian without a vault`)\n }\n obsidianOptions.vaultCopy = vaultCopy; // for use in getVaultPath() and the other service hooks\n }\n\n /**\n * Sets up the --user-data-dir for the Electron app. Sets the obsidian.json in the dir to open the vault on boot.\n */\n private async electronSetupConfigDir(cap: WebdriverIO.Capabilities) {\n const obsidianOptions = getNormalizedObsidianOptions(cap);\n const configDir = await this.obsidianLauncher.setupConfigDir({\n appVersion: obsidianOptions.appVersion, installerVersion: obsidianOptions.installerVersion,\n appPath: obsidianOptions.appPath,\n vault: obsidianOptions.vaultCopy,\n // `app.emulateMobile` just sets this localStorage variable. Setting it ourselves here instead of calling\n // the function simplifies the boot/plugin load sequence and makes sure plugins load in mobile mode.\n localStorage: obsidianOptions.emulateMobile ? {\"EmulateMobile\": \"1\"} : {},\n });\n this.tmpDirs.push(configDir);\n\n cap['goog:chromeOptions'] = {\n ...cap['goog:chromeOptions'],\n args: [\n `--user-data-dir=${configDir}`,\n ...(cap['goog:chromeOptions']?.args ?? []).filter(arg => {\n const match = arg.match(/^--user-data-dir=(.*)$/);\n return !match || !this.tmpDirs.includes(match[1]);\n })\n ]\n }\n }\n\n /**\n * Sets up the Obsidian app. Installs and launches it if needed, and sets permissions and contexts.\n * \n * You can configure Appium to install and launch the app for you. However, if you do that it reboots the app after\n * every session/spec which is really slow. So we are handling the app setup manually here.\n */\n private async appiumSetupApp() {\n const browser = this.browser!;\n const appiumOptions = getAppiumOptions(browser.requestedCapabilities);\n const obsidianOptions = getNormalizedObsidianOptions(browser.requestedCapabilities);\n const appId = \"md.obsidian\";\n\n // install/reinstall Obsidian if needed\n const dumpsys: string = await browser.execute(\"mobile: shell\", {command: \"dumpsys\", args: [\"package\", appId]});\n const installedObsidianVersion = dumpsys.match(/versionName[=:](.*)$/m)?.[1]?.trim();\n if (installedObsidianVersion != obsidianOptions.appVersion) {\n await browser.execute('mobile: terminateApp', {appId}); // terminate app (if running)\n await browser.execute('mobile: removeApp', {appId, keepData: false}); // uninstall (if present)\n await browser.execute('mobile: installApp', {\n appPath: appiumOptions.app, // the APK\n timeout: appiumOptions.androidInstallTimeout, // respect appium configuration\n grantPermissions: true, // this and autoGrantPermissions don't seem to really work, see below\n });\n }\n\n // start app if needed\n const appState: number = await browser.execute(\"mobile: queryAppState\", {appId});\n if (appState < 4) { // 0: not installed, 1: not running, 3: running in background, 4: running in foreground\n await browser.execute(\"mobile: activateApp\", {appId});\n }\n\n // grant Obsidian the permissions it needs, mainly file access.\n // appium:autoGrantPermissions is supposed to automatically grant everything it needs, but I can't get it to\n // work. The \"mobile: changePermissions\" \"all\" option also doesn't seem to work here. I have to explicitly list\n // the permissions and use the \"appops\" target. See https://github.com/appium/appium/issues/19991\n // I'm calling \"mobile: changePermissions\" \"all\" as well just in case it is actually doing something\n await browser.execute(\"mobile: changePermissions\", {\n action: \"grant\",\n appPackage: appId,\n permissions: \"all\",\n });\n await browser.execute(\"mobile: changePermissions\", {\n action: \"allow\",\n appPackage: appId,\n permissions: [ // these are from apk AndroidManifest.xml (extracted with apktool)\n \"READ_EXTERNAL_STORAGE\", \"WRITE_EXTERNAL_STORAGE\", \"MANAGE_EXTERNAL_STORAGE\",\n ],\n target: \"appops\", // requires appium --allow-insecure adb_shell\n });\n\n // switch to the webview context\n const context = \"WEBVIEW_md.obsidian\";\n await browser.waitUntil(async () => (await browser.getContexts() as string[]).includes(context));\n await browser.switchContext(context);\n\n // Clear app state\n await browser.execute(() => { // in case tests get interrupted during the `_capacitor_file_` bit\n if (window.location.href != \"http://localhost/\") {\n window.location.replace('http://localhost/');\n }\n })\n if (!obsidianOptions.vault) { // skip if vault is set, as we'll clear stat when we open the new vault\n await this.appiumCloseVault();\n }\n }\n\n /**\n * Opens the vault in appium.\n */\n private async appiumOpenVault() {\n const browser = this.browser!;\n const obsidianOptions = getNormalizedObsidianOptions(browser.requestedCapabilities);\n const androidVault = `${this.androidVaultDir}/${path.basename(obsidianOptions.vaultCopy!)}`;\n // TODO: Capabilities is not really the right place to be storing state like vaultCopy and uploadVault\n obsidianOptions.uploadedVault = androidVault;\n // transfer the vault to the device\n await appiumUploadFiles(browser, {src: obsidianOptions.vaultCopy!, dest: androidVault});\n\n // open vault by setting the localStorage keys and relaunching Obsidian\n // on appium restarting the app with appium:fullReset is *really* slow. And, unlike electron we can actually\n // switch vault with just a reload. So for Appium, instead of rebooting we manually wipe localStorage and use\n // reload to switch the vault.\n await browser.execute(async (androidVault) => {\n localStorage.clear();\n localStorage.setItem('mobile-external-vaults', JSON.stringify([androidVault]));\n localStorage.setItem('mobile-selected-vault', androidVault);\n // appId on mobile is just the full vault path\n localStorage.setItem(`enable-plugin-${androidVault}`, 'true');\n window.location.reload();\n }, androidVault);\n }\n\n /**\n * Close any open vault and go back to the vault switcher\n */\n private async appiumCloseVault() {\n const browser = this.browser!;\n await browser.execute(async () => {\n if (localStorage.length > 0) { // skip if we're already on the vault switcher\n localStorage.clear();\n location.reload();\n }\n });\n }\n\n /**\n * Waits for Obsidian to be ready, and does some other final setup.\n */\n private async prepareApp() {\n const browser = this.browser!;\n const obsidianOptions = getNormalizedObsidianOptions(browser.requestedCapabilities);\n\n if (obsidianOptions.emulateMobile && obsidianOptions.vault != undefined) {\n // I don't think this is technically necessary, but when you set the window size via emulateMobile it sets\n // the window size in the browser view, but not the actual window size which looks weird and makes it hard\n // to manually debug a paused test. The normal ways to set window size don't work on Obsidian. Obsidian\n // doesn't respect the `--window-size` argument, and wdio setViewport and setWindowSize don't work without\n // BiDi. This resizes the window directly using electron APIs.\n await browser.waitUntil(() => browser.execute(() => !!(window as any).electron));\n const [width, height] = await browser.execute(() => [window.innerWidth, window.innerHeight]);\n await browser.execute(async (width, height) => {\n await (window as any).electron.remote.getCurrentWindow().setSize(width, height);\n }, width, height);\n }\n\n // wait until app is loaded\n if (obsidianOptions.vault) {\n await browser.waitUntil( // wait until the helper plugin is loaded\n () => browser.execute(() => !!(window as any).wdioObsidianService),\n {timeout: 30 * 1000, interval: 100},\n );\n await browser.executeObsidian(async ({app}) => {\n await new Promise<void>((resolve) => app.workspace.onLayoutReady(resolve));\n });\n } else {\n await browser.execute(async () => {\n if (document.readyState === \"loading\") {\n return new Promise<void>(resolve => document.addEventListener(\"DOMContentLoaded\", () => resolve()));\n }\n });\n }\n }\n\n private createReloadObsidian() {\n const service = this; // eslint-disable-line @typescript-eslint/no-this-alias\n const reloadObsidian: WebdriverIO.Browser['reloadObsidian'] = async function(\n this: WebdriverIO.Browser,\n {vault, plugins, theme} = {},\n ) {\n const oldObsidianOptions = getNormalizedObsidianOptions(this.requestedCapabilities);\n const selectedPlugins = selectPlugins(oldObsidianOptions.plugins, plugins);\n const selectedThemes = selectThemes(oldObsidianOptions.themes, theme);\n if (!vault && oldObsidianOptions.vaultCopy == undefined) {\n throw Error(`No vault is open, pass a vault path to reloadObsidian`);\n }\n const newObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...oldObsidianOptions,\n // Resolve relative to PWD instead of root dir during tests\n vault: vault ? path.resolve(vault) : oldObsidianOptions.vault,\n plugins: selectedPlugins, themes: selectedThemes,\n };\n\n if (isAppium(this.requestedCapabilities)) {\n this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] = newObsidianOptions;\n if (vault) {\n await service.setupVault(this.requestedCapabilities);\n await service.appiumOpenVault();\n } else {\n // reload without resetting app state or triggering appium:fullReset\n\n // hack to disable the Obsidian app and make sure it doesn't write to the vault while we modify it\n await this.execute(() => {\n window.location.replace('http://localhost/_capacitor_file_/not-a-file');\n });\n\n // while Obsidian is down, modify the vault files to setup plugins and themes\n const local = path.join(oldObsidianOptions.vaultCopy!, \".obsidian\");\n const localCommunityPlugins = path.join(local, \"community-plugins.json\");\n const localAppearance = path.join(local, \"appearance.json\");\n const remote = `${oldObsidianOptions.uploadedVault!}/.obsidian`;\n const remoteCommunityPlugins = `${remote}/community-plugins.json`;\n const remoteAppearance = `${remote}/appearance.json`;\n\n await appiumDownloadFile(this, remoteCommunityPlugins, localCommunityPlugins).catch(() => {});\n await appiumDownloadFile(this, remoteAppearance, localAppearance).catch(() => {});\n await service.obsidianLauncher.setupVault({\n vault: oldObsidianOptions.vaultCopy!, copy: false,\n plugins: selectedPlugins, themes: selectedThemes,\n });\n let files = [localCommunityPlugins, localAppearance];\n files = (await Promise.all(files.map(async f => await fileExists(f) ? f : \"\"))).filter(f => f);\n await appiumUploadFiles(this, {src: local, dest: remote, files});\n\n // switch the app back\n await this.execute(() => {\n window.location.replace('http://localhost/');\n })\n }\n } else {\n // if browserName is set, reloadSession tries to restart the driver entirely, so unset those\n const newCap: WebdriverIO.Capabilities = _.cloneDeep(\n _.omit(this.requestedCapabilities, ['browserName', 'browserVersion'])\n );\n newCap[OBSIDIAN_CAPABILITY_KEY] = newObsidianOptions;\n\n if (vault) {\n await service.setupVault(newCap);\n await service.electronSetupConfigDir(newCap);\n await this.reloadSession(newCap);\n } else {\n // reload preserving current vault and config dir\n\n // Obsidian debounces saves to the config dir, and so changes to configuration made in the tests may\n // not get saved to disk before the reboot. I haven't found a better way to flush everything than\n // just waiting a bit.\n await this.pause(2000);\n\n await this.deleteSession({shutdownDriver: false});\n // while Obsidian is down, modify the vault files to setup plugins and themes\n await service.obsidianLauncher.setupVault({\n vault: oldObsidianOptions.vaultCopy!, copy: false,\n plugins: selectedPlugins, themes: selectedThemes,\n });\n await this.reloadSession(newCap);\n }\n }\n await service.prepareApp();\n };\n return reloadObsidian;\n }\n\n /**\n * Runs before the session and browser have started.\n */\n async beforeSession(config: Options.Testrunner, cap: WebdriverIO.Capabilities) {\n try {\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n if (cap[OBSIDIAN_CAPABILITY_KEY].vault != undefined) {\n await this.setupVault(cap);\n }\n if (!isAppium(cap)) {\n await this.electronSetupConfigDir(cap);\n }\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n\n /**\n * Runs after session and browser have started, but before tests.\n */\n async before(cap: WebdriverIO.Capabilities, specs: unknown, browser: WebdriverIO.Browser) {\n this.browser = browser;\n try {\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n\n // You are supposed to add commands via the addCommand hook, however you can't add synchronous methods that\n // way. Also, addCommand completely breaks the stack traces of errors from the methods, while tacking on the\n // methods manually doesn't.\n const newBrowserCommands = {\n ...browserCommands,\n reloadObsidian: this.createReloadObsidian(),\n }\n for (const [name, cmd] of Object.entries(newBrowserCommands)) {\n (browser as any)[name] = cmd;\n }\n\n if (isAppium(browser.requestedCapabilities)) {\n await this.appiumSetupApp();\n if (cap[OBSIDIAN_CAPABILITY_KEY].vault) {\n await this.appiumOpenVault();\n }\n }\n await this.prepareApp();\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n\n /** Runs after tests are done, but before the session is shut down */\n async after(result: number, cap: WebdriverIO.Capabilities) {\n const browser = this.browser!;\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n\n if (isAppium(cap)) {\n await this.appiumCloseVault();\n // \"mobile: deleteFile\" doesn't work on folders. \"mobile:shell\" requires appium --allow-insecure adb_shell\n await browser.execute(\"mobile: shell\", {command: \"rm\", args: [\"-rf\", this.androidVaultDir]});\n }\n }\n\n /**\n * Cleanup\n */\n async afterSession() {\n for (const tmpDir of this.tmpDirs) {\n await fsAsync.rm(tmpDir, { recursive: true, force: true });\n }\n }\n}\n","import * as path from \"path\"\nimport * as fsAsync from \"fs/promises\"\nimport * as crypto from \"crypto\";\nimport { fileURLToPath } from \"url\";\nimport { TFile } from \"obsidian\";\nimport _ from \"lodash\";\nimport { OBSIDIAN_CAPABILITY_KEY, NormalizedObsidianCapabilityOptions } from \"../types.js\";\nimport { BasePage } from \"./basePage.js\";\nimport { isAppium, normalizePath, isHidden, isText } from \"../utils.js\";\nimport { AppInternal } from \"../obsidianTypes.js\";\n\n\n/**\n * Class with various helper methods for writing Obsidian tests using the\n * [page object pattern](https://webdriver.io/docs/pageobjects).\n * \n * You can get an instance of this class either by running\n * ```ts\n * const obsidianPage = await browser.getObsidianPage();\n * ```\n * or just importing it directly with\n * ```ts\n * import { obsidianPage } from \"wdio-obsidian-service\";\n * ```\n * \n * @hideconstructor\n * @category Utilities\n */\nclass ObsidianPage extends BasePage {\n private getObsidianCapabilities(): NormalizedObsidianCapabilityOptions {\n return this.browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];\n }\n\n /**\n * Returns the path to the vault opened in Obsidian.\n * \n * wdio-obsidian-service copies your vault before running tests, so this is the path to the temporary copy.\n */\n getVaultPath(): string {\n const obsidianOptions = this.getObsidianCapabilities();\n if (obsidianOptions.vaultCopy === undefined) {\n throw Error(\"No vault open, set vault in wdio.conf.mts or use reloadObsidian to open a vault dynamically.\")\n }\n return obsidianOptions.uploadedVault ?? obsidianOptions.vaultCopy;\n }\n\n /**\n * Return the Obsidian config dir (just \".obsidian\" unless you changed the config dir name in settings).\n */\n async getConfigDir(): Promise<string> {\n return await this.browser.executeObsidian(({app}) => app.vault.configDir)\n }\n\n /**\n * Returns the Obsidian Platform object. Useful for skipping tests based on whether you are running in desktop or\n * mobile, or based on OS.\n */\n async getPlatform(): Promise<Platform> {\n const obsidianOptions = this.getObsidianCapabilities();\n if (obsidianOptions.vault !== undefined) {\n return await this.browser.executeObsidian(({obsidian}) => {\n const p = obsidian.Platform;\n return {\n isDesktop: p.isDesktop,\n isMobile: p.isMobile,\n isDesktopApp: p.isDesktopApp,\n isMobileApp: p.isMobileApp,\n isIosApp: p.isIosApp,\n isAndroidApp: p.isAndroidApp,\n isPhone: p.isPhone,\n isTablet: p.isTablet,\n isMacOS: p.isMacOS,\n isWin: p.isWin,\n isLinux: p.isLinux,\n isSafari: p.isSafari,\n };\n });\n } else {\n // hack to allow calling getPlatform before opening a vault. This is needed so you can use getPlatform to\n // skip a test before wasting time opening the vault. we don't use this method the rest of the time as you\n // can technically change the size or switch emulation mode during tests\n const appium = isAppium(this.browser.requestedCapabilities);\n const emulateMobile = obsidianOptions.emulateMobile;\n const [width, height] = await this.browser.execute(() => [window.innerWidth, window.innerHeight]);\n\n let isTablet = false;\n let isPhone = false;\n if (appium || emulateMobile) {\n // replicate Obsidian's tablet vs phone breakpoint\n isTablet = (width >= 600 && height >= 600);\n isPhone = !isTablet;\n }\n\n return {\n isDesktop: !(appium || emulateMobile),\n isMobile: appium || emulateMobile,\n isDesktopApp: !appium,\n isMobileApp: appium,\n isIosApp: false, // iOS is not supported\n isAndroidApp: appium,\n isPhone: isPhone,\n isTablet: isTablet,\n isMacOS: !appium && process.platform == 'darwin',\n isWin: !appium && process.platform == 'win32',\n isLinux: !appium && process.platform == 'linux',\n isSafari: false, // iOS is not supported\n };\n }\n }\n\n /**\n * Enables a plugin by ID\n */\n async enablePlugin(pluginId: string): Promise<void> {\n await this.browser.executeObsidian(\n async ({app}, pluginId) => await (app as AppInternal).plugins.enablePluginAndSave(pluginId),\n pluginId,\n );\n }\n\n /**\n * Disables a plugin by ID\n */\n async disablePlugin(pluginId: string): Promise<void> {\n await this.browser.executeObsidian(\n async ({app}, pluginId) => await (app as AppInternal).plugins.disablePluginAndSave(pluginId),\n pluginId,\n );\n }\n\n /**\n * Sets the theme. Pass \"default\" to reset to the Obsidian theme.\n */\n async setTheme(themeName: string): Promise<void> {\n themeName = themeName == 'default' ? '' : themeName;\n await this.browser.executeObsidian(\n async ({app}, themeName) => await (app as AppInternal).customCss.setTheme(themeName),\n themeName,\n )\n }\n\n /**\n * Opens a file in a new tab.\n */\n async openFile(path: string) {\n await this.browser.executeObsidian(async ({app, obsidian}, path) => {\n const file = app.vault.getAbstractFileByPath(path);\n if (file instanceof obsidian.TFile) {\n const leaf = app.workspace.getLeaf('tab');\n await leaf.openFile(file);\n app.workspace.setActiveLeaf(leaf, {focus: true});\n } else {\n throw Error(`No file ${path} exists`);\n }\n }, path)\n }\n\n /**\n * Loads a saved workspace layout from `.obsidian/workspaces.json` by name. Use the core \"Workspaces\"\n * plugin to create the layouts. You can also pass the layout object directly.\n */\n async loadWorkspaceLayout(layout: any): Promise<void> {\n if (typeof layout == \"string\") {\n // read from .obsidian/workspaces.json like the built-in workspaces plugin does\n const workspacesPath = `${await this.getConfigDir()}/workspaces.json`;\n const layoutName = layout;\n try {\n const fileContent = await this.browser.executeObsidian(async ({app}, workspacesPath) => {\n return await app.vault.adapter.read(workspacesPath);\n }, workspacesPath);\n layout = JSON.parse(fileContent)?.workspaces?.[layoutName];\n } catch {\n throw new Error(`Failed to load ${workspacesPath}:${layoutName}`);\n }\n if (!layout) {\n throw new Error(`No workspace ${layoutName} found in ${workspacesPath}`);\n }\n }\n\n await this.browser.executeObsidian(async ({app}, layout) => {\n await app.workspace.changeLayout(layout)\n }, layout)\n }\n\n /**\n * Return contents of a file in the vault.\n */\n async read(file: string): Promise<string> {\n return await this.browser.executeObsidian(async ({app, obsidian}, file) => {\n file = obsidian.normalizePath(file);\n return await app.vault.adapter.read(file);\n }, file);\n }\n\n /**\n * Return contents of a binary file in the vault.\n */\n async readBinary(file: string): Promise<ArrayBuffer> {\n const b64Contents = await this.browser.executeObsidian(async ({app, obsidian}, file) => {\n function toB64(buffer: ArrayBuffer) {\n let binary = ''\n const bytes = new Uint8Array(buffer)\n const len = bytes.byteLength\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(bytes[i])\n }\n return btoa(binary)\n }\n\n file = obsidian.normalizePath(file);\n return toB64(await app.vault.adapter.readBinary(file));\n }, file);\n return Buffer.from(b64Contents, 'base64').buffer;\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|ArrayBufferLike|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|ArrayBufferLike|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 = {\n type: \"file\"|\"folder\",\n sourceContent?: string|ArrayBufferLike|ArrayBuffer,\n sourceFile?: string,\n };\n const newVault: Map<string, NewFileInfo> = new Map();\n for (let vault of vaults) {\n if (typeof vault == \"string\") {\n vault = path.resolve(vault);\n const files = await fsAsync.readdir(vault, { recursive: true, withFileTypes: true });\n for (const f of files) {\n const fullPath = path.join(f.parentPath, f.name);\n const vaultPath = path.relative(vault, fullPath).split(path.sep).join(\"/\");\n if (!vaultPath.startsWith(configDir + \"/\")) {\n if (f.isDirectory()) {\n newVault.set(vaultPath, {type: 'folder'});\n } else {\n newVault.set(vaultPath, {type: 'file', sourceFile: fullPath});\n }\n }\n }\n } else {\n vault = _.mapKeys(vault, (v, k) => normalizePath(k));\n for (const [file, content] of Object.entries(vault)) {\n newVault.set(file, {type: \"file\", sourceContent: content});\n }\n const folders = new Set(Object.keys(vault).map(p => path.posix.dirname(p)).filter(p => p !== \".\"));\n for (const folder of folders) {\n newVault.set(folder, {type: \"folder\"});\n }\n }\n }\n\n // list all files in the current vault\n type CurrFileInfo = {type: \"file\"|\"folder\", hash?: string};\n const currVault = new Map(await this.browser.executeObsidian(({app}, configDir) => {\n async function hash(data: ArrayBuffer) {\n const hashBuffer = await window.crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n }\n\n async function listRecursive(path: string): Promise<[string, CurrFileInfo][]> {\n const result: [string, CurrFileInfo][] = [];\n const { folders, files } = await app.vault.adapter.list(path);\n for (const folder of folders) {\n if (!folder.startsWith(configDir + \"/\")) {\n result.push([folder, {type: \"folder\"}]);\n result.push(...await listRecursive(folder));\n }\n }\n for (const file of files) {\n if (!file.startsWith(configDir + \"/\")) {\n let fileHash = (app as AppInternal).metadataCache.getFileInfo(file)?.hash;\n if (!fileHash) { // hidden files etc. aren't in Obsidian's metadata cache\n fileHash = await hash(await app.vault.adapter.readBinary(file));\n }\n result.push([file, { type: \"file\", hash: fileHash }]);\n }\n }\n return result;\n }\n\n return listRecursive(\"/\");\n }, configDir));\n\n // delete any files that need to be deleted\n for (const file of [...currVault.keys()].sort().reverse()) {\n const currFileInfo = currVault.get(file)!\n if (!newVault.has(file) || newVault.get(file)!.type != currFileInfo.type) {\n await this.delete(file);\n }\n }\n\n // create files and folders\n for (const [file, newFileInfo] of _.sortBy([...newVault.entries()], 0)) {\n const currFileInfo = currVault.get(file);\n if (newFileInfo.type == \"file\") {\n let content = newFileInfo.sourceContent;\n if (!content) {\n content = (await fsAsync.readFile(newFileInfo.sourceFile!)).buffer as ArrayBuffer;\n }\n const hash = crypto.createHash(\"SHA256\")\n .update(typeof content == \"string\" ? content : new Uint8Array(content))\n .digest(\"hex\");\n if (!currFileInfo || currFileInfo.hash != hash) {\n await this.write(file, content);\n }\n } else if (newFileInfo.type == \"folder\" && !currFileInfo) {\n await this.mkdir(file);\n }\n }\n }\n}\n\n/**\n * Info on the platform we are running on or emulating, in similar format as\n * [obsidian.Platform](https://docs.obsidian.md/Reference/TypeScript+API/Platform)\n * @category Types\n */\nexport interface Platform {\n /**\n * The UI is in desktop mode.\n */\n isDesktop: boolean;\n /**\n * The UI is in mobile mode.\n */\n isMobile: boolean;\n /**\n * We're running the Electron-based desktop app.\n * Note, when running under `emulateMobile` this will still be true and isDesktop will be false.\n */\n isDesktopApp: boolean;\n /**\n * We're running the Capacitor-js mobile app.\n * Note, when running under `emulateMobile` this will still be false and isMobile will be true.\n */\n isMobileApp: boolean;\n /** \n * We're running the iOS app.\n * Note, wdio-obsidian-service doesn't support iOS yet, so this will always be false.\n */\n isIosApp: boolean;\n /**\n * We're running the Android app.\n */\n isAndroidApp: boolean;\n /**\n * We're in a mobile app that has very limited screen space.\n */\n isPhone: boolean;\n /**\n * We're in a mobile app that has sufficiently large screen space.\n */\n isTablet: boolean;\n /**\n * We're on a macOS device, or a device that pretends to be one (like iPhones and iPads).\n * Typically used to detect whether to use command-based hotkeys vs ctrl-based hotkeys.\n */\n isMacOS: boolean;\n /**\n * We're on a Windows device.\n */\n isWin: boolean;\n /**\n * We're on a Linux device.\n */\n isLinux: boolean;\n /**\n * We're running in Safari.\n * Note, wdio-obsidian-service doesn't support iOS yet, so this will always be false.\n */\n isSafari: boolean;\n};\n\n/**\n * Instance of {@link ObsidianPage} with helper methods for writing Obsidian tests.\n * @category Utilities\n */\nconst obsidianPage = new ObsidianPage()\nexport default obsidianPage;\nexport { ObsidianPage };\n","import type { ObsidianBrowserCommands } from \"./browserCommands.js\";\nimport type { PluginEntry, ThemeEntry, DownloadedPluginEntry, DownloadedThemeEntry } from \"obsidian-launcher\";\n\nexport const OBSIDIAN_CAPABILITY_KEY = \"wdio:obsidianOptions\";\n\n/**\n * Options passed to an \"wdio:obsidianOptions\" capability in wdio.conf.mts. E.g.\n * ```ts\n * // ...\n * capabilities: [{\n * browserName: \"obsidian\",\n * browserVersion: \"latest\",\n * 'wdio:obsidianOptions': {\n * installerVersion: 'earliest',\n * plugins: [\".\"],\n * },\n * }],\n * ```\n * \n * @category Options\n */\nexport interface ObsidianCapabilityOptions {\n /**\n * Version of Obsidian to download and run.\n * \n * Can be set to a specific version or one of:\n * - \"latest\": Run the latest non-beta Obsidian version\n * - \"latest-beta\": Run the latest beta Obsidian version (or latest if there is no current beta)\n * - To download Obsidian beta versions you'll need to have an Obsidian Insiders account and either set the \n * `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` environment variables (`.env` file is supported) or pre-download\n * the Obsidian beta version with `npx obsidian-launcher download app -v latest-beta`\n * - \"earliest\": Run the `minAppVersion` set in your `manifest.json`\n * \n * Defaults to \"latest\".\n * \n * You can also use the wdio capability `browserVersion` field to set the Obsidian version.\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n */\n appVersion?: string\n\n /**\n * Version of the Obsidian installer to download and run.\n * \n * Obsidian is Desktop distributed in two parts, the app which contains the JS, and the installer which is the\n * binary with Electron. Obsidian's auto update only updates the app, so users on the same Obsidian version can be\n * running different Electron versions. You can use this to test your plugin against different installer/Electron\n * versions.\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n *\n * Can be set to a specific version string or one of:\n * - \"latest\": Run the latest Obsidian installer compatible with `appVersion`.\n * - \"earliest\": Run the oldest Obsidian installer compatible with `appVersion`.\n * \n * Defaults to \"earliest\".\n */\n installerVersion?: string,\n\n /**\n * List of plugins to install.\n * \n * Each entry is a path to the local plugin to install, e.g. [\".\"] or [\"dist\"] depending on your build setup. Paths\n * are relative to your `wdio.conf.mts`. You can also pass objects. If you pass an object it should contain one of\n * `path` (to install a local plugin), `repo` (to install a plugin from GitHub), or `id` (to install a community\n * plugin). You can set `enabled: false` to install the plugin but start it disabled. You can enable the plugin\n * later using {@link ObsidianBrowserCommands.reloadObsidian|browser.reloadObsidian} or\n * {@link ObsidianPage.enablePlugin}.\n */\n plugins?: PluginEntry[],\n\n /**\n * List of themes to install.\n * \n * Each entry is a path to the local theme to install. Paths are relative to your `wdio.conf.mts`. You can also pass\n * an object. If you pass an object it should contain one of `path` (to install a local theme), `repo` (to install a\n * theme from GitHub), or `name` (to install a community theme). You can set `enabled: false` to install the theme,\n * but start it disabled. You can only have one enabled theme, so if you pass multiple you'll have to disable all\n * but one.\n */\n themes?: ThemeEntry[],\n\n /**\n * The path to the vault to open.\n * \n * The vault will be copied, so any changes made in your tests won't affect the original. If omitted, no vault will\n * be opened and you'll need to call {@link ObsidianBrowserCommands.reloadObsidian|browser.reloadObsidian} to open a\n * vault during your tests. Path is relative to your `wdio.conf.mts`.\n */\n vault?: string,\n\n /**\n * Set to true to emulate mobile on the Electron desktop app. This uses Obsidian `app.emulateMobile()` to switch\n * Obsidian to the mobile UI, and you can use Chrome's mobileEmulation to set the screen size. You can compare\n * tablet vs phone UIs by setting the screen size or emulated device. Obsidian tablet UI triggers at\n * width/height >= 600.\n *\n * Note that Obsidian Mobile runs on Capacitor instead of Electron so there are various platform differences that\n * can't be emulated. But it's good enough for most cases as long as you aren't interacting directly with the\n * operating system or Electron APIs. You can use an Android Virtual Device instead if you want a more accurate\n * (but slower) mobile test.\n *\n * See [Mobile Emulation](../README.md#mobile-emulation) for more info.\n * See [Android](../README.md#android) if you want to test the real mobile app on an Android Virtual Device instead.\n */\n emulateMobile?: boolean,\n\n /**\n * Path to the Obsidian binary to use. If omitted it will be downloaded automatically.\n */\n binaryPath?: string,\n\n /**\n * Path to the app asar to load into obsidian. If omitted it will be downloaded automatically.\n */\n appPath?: string,\n}\n\n\n/** Internal type, capability options after being normalized by onPrepare */\nexport interface NormalizedObsidianCapabilityOptions {\n appVersion: string\n installerVersion: string,\n plugins: DownloadedPluginEntry[],\n themes: DownloadedThemeEntry[],\n vault?: string,\n vaultCopy?: string,\n /** Path of the vault on the appium device */\n uploadedVault?: string,\n emulateMobile: boolean,\n binaryPath?: string,\n appPath?: string,\n}\n\n\n/**\n * Options passed to wdio-obsidian-service service in wdio.conf.mts. E.g.\n * ```js\n * // ...\n * services: [[\"obsidian\", {versionsUrl: \"file:///path/to/obsidian-versions.json\"}]]\n * ```\n * You'll usually want to leave these options as the default, they are mostly useful for wdio-obsidian-service's\n * internal tests.\n * \n * @category Options\n */\nexport interface ObsidianServiceOptions {\n /**\n * Override the `obsidian-versions.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/jesse-r-s-hines/wdio-obsidian-service/HEAD/obsidian-versions.json\n * which is auto-updated to contain information on available Obsidian versions.\n */\n versionsUrl?: string,\n /**\n * Override the `community-plugins.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-plugins.json\n */\n communityPluginsUrl?: string,\n /**\n * Override the `community-css-themes.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-css-themes.json\n */\n communityThemesUrl?: string,\n}\n\ndeclare global {\n namespace WebdriverIO {\n interface Capabilities {\n [OBSIDIAN_CAPABILITY_KEY]?: ObsidianCapabilityOptions,\n }\n\n interface Browser extends ObsidianBrowserCommands {}\n }\n}\n","// src/index.ts\nvar globals = globalThis._wdioGlobals = globalThis._wdioGlobals || /* @__PURE__ */ new Map();\nvar GLOBALS_ERROR_MESSAGE = `No browser instance registered. Don't import @wdio/globals outside of the WDIO testrunner context. Or you have two two different \"@wdio/globals\" packages installed.`;\nfunction proxyHandler(key) {\n return {\n get: (self, prop) => {\n if (!globals.has(key)) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n const receiver = globals.get(key);\n const field = receiver[prop];\n return typeof field === \"function\" ? field.bind(receiver) : field;\n }\n };\n}\nvar browser = new Proxy(\n class Browser {\n },\n proxyHandler(\"browser\")\n);\nvar driver = new Proxy(\n class Browser2 {\n },\n proxyHandler(\"driver\")\n);\nvar multiremotebrowser = new Proxy(\n class Browser3 {\n },\n proxyHandler(\"multiremotebrowser\")\n);\nvar $ = (...args) => {\n if (!globals.has(\"$\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"$\")(...args);\n};\nvar $$ = (...args) => {\n if (!globals.has(\"$$\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"$$\")(...args);\n};\nvar expect = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\")(...args);\n};\nvar ASYNC_MATCHERS = [\n \"any\",\n \"anything\",\n \"arrayContaining\",\n \"objectContaining\",\n \"stringContaining\",\n \"stringMatching\"\n];\nfor (const matcher of ASYNC_MATCHERS) {\n expect[matcher] = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\")[matcher](...args);\n };\n}\nexpect.not = ASYNC_MATCHERS.reduce((acc, matcher) => {\n acc[matcher] = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\").not[matcher](...args);\n };\n return acc;\n}, {});\nexpect.extend = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n const expect2 = globals.get(\"expect\");\n return expect2.extend(...args);\n};\nfunction _setGlobal(key, value, setGlobal = true) {\n globals.set(key, value);\n if (setGlobal) {\n globalThis[key] = value;\n }\n}\nexport {\n $,\n $$,\n _setGlobal,\n browser,\n driver,\n expect,\n multiremotebrowser\n};\n","import * as wdioGlobals from \"@wdio/globals\"\n\n/**\n * Base page object for use in the wdio [page object pattern](https://webdriver.io/docs/pageobjects).\n * \n * You can pass the browser to the page object, which allows using the object even in wdio standalone mode.\n */\nexport class BasePage {\n private _browser: WebdriverIO.Browser|undefined\n constructor(browser?: WebdriverIO.Browser) {\n this._browser = browser;\n }\n\n /**\n * Returns the browser instance.\n * @hidden\n */\n protected get browser(): WebdriverIO.Browser {\n return this._browser ?? wdioGlobals.browser;\n }\n}\n","import path from \"path\";\nimport crypto from \"crypto\";\nimport fsAsync from \"fs/promises\";\nimport * as tar from \"tar\";\nimport _ from \"lodash\";\n\n/** Quote string for use in shell scripts */\nexport function quote(input: string) {\n return `'${input.replace(/'/g, \"'\\\\''\")}'`;\n}\n\nexport async function fileExists(path: string) {\n try {\n await fsAsync.access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Gets the appium options.\n * Handles combining `\"appium:foo\"` and `\"appium:options\": {\"foo\": ...}` style options.\n */\nexport function getAppiumOptions(\n cap: WebdriverIO.Capabilities,\n): Exclude<WebdriverIO.Capabilities['appium:options'], undefined> {\n let result = _.pickBy(cap, (v, k) => k.startsWith(\"appium:\") && k != 'appium:options');\n result = _.mapKeys(result, (v, k) => k.slice(7))\n return {...result, ...cap['appium:options']};\n}\n\n/** Returns true if this capability is for Appium */\nexport function isAppium(cap: WebdriverIO.Capabilities) {\n return getAppiumOptions(cap).automationName?.toLocaleLowerCase() === 'uiautomator2';\n}\n\n/**\n * Upload multiple files at once. Uploads files as a tar for much better performance and to avoid issues with special\n * characters in names. (see https://github.com/appium/appium-android-driver/issues/1004)\n * \n * @param opts.src Directory to copy from (-C in tar)\n * @param opts.dest Directory to copy to\n * @param opts.files Files under src to copy. Defaults to the whole dir.\n * @param opts.chunkSize Size to split the tar into while uploading (to avoid excessive RAM usage)\n */\nexport async function appiumUploadFiles(browser: WebdriverIO.Browser, opts: {\n src: string, dest: string, files?: string[], chunkSize?: number,\n}) {\n let {\n src, dest,\n files = [src],\n chunkSize = 2 * 1024 * 1024,\n } = opts;\n src = path.resolve(src);\n dest = path.posix.normalize(dest).replace(/\\/$/, '');\n files = files.map(f => path.relative(src, f) || \".\");\n if (files.length == 0) return; // nothing to do\n\n // We'll tar the files up and then extract the tar on Android side. This is a lot faster than doing many small\n // pushFiles. Since pushFile requires sending the whole file contents as a base64 string, we'll split up the tar\n // into chunks to keep the size manageable for large vaults.\n\n const tmpDir = \"/data/local/tmp\"\n const slug = crypto.randomBytes(10).toString(\"base64url\").replace(/[-_]/g, '0');\n const stream = tar.create({C: src, gzip: { level: 2 }}, files);\n\n // buffer/chunk size of the tar stream varies. Without compression it appears to be one chunk per file, splitting\n // large files at maxReadSize, plus some chunks for the header. However compression adds another layer that appears\n // to limit the output chunk size to about 16K.\n let buffers: Buffer[] = [];\n let bufferSize = 0;\n let i = 0;\n for await (const chunk of stream) {\n if (bufferSize + chunk.length > chunkSize && bufferSize > 0) {\n const data = Buffer.concat(buffers).toString('base64');\n await browser.pushFile(`${tmpDir}/${slug}-${String(i).padStart(6, '0')}.tar`, data);\n i++;\n buffers = [];\n bufferSize = 0;\n }\n buffers.push(chunk);\n bufferSize += chunk.length;\n }\n const data = Buffer.concat(buffers).toString('base64');\n await browser.pushFile(`${tmpDir}/${slug}-${String(i).padStart(6, '0')}.tar`, data);\n\n // extract the tar. `mobile: shell` does NOT escape arguments despite taking an argument array.\n await browser.execute(\"mobile: shell\", {command: \"sh\", args: [\"-c\", quote(`\n mkdir -p ${quote(dest)};\n cat ${tmpDir}/${slug}-*.tar | tar -xz -C ${quote(dest)};\n rm ${tmpDir}/${slug}-*.tar;\n `)]});\n}\n\nexport async function appiumDownloadFile(browser: WebdriverIO.Browser, src: string, dest: string) {\n src = path.posix.normalize(src);\n dest = path.resolve(dest);\n const content = Buffer.from(await browser.pullFile(src), \"base64\");\n await fsAsync.writeFile(dest, content);\n}\n\n/** Lists all files under a folder. Returns full paths. */\nexport async function appiumReaddir(browser: WebdriverIO.Browser, dir: string) {\n // list all files, one per line, no escaping\n const stdout: string = await browser.execute(\"mobile: shell\", {command: \"ls\", args: [\"-NA1\", dir]});\n return stdout.split(\"\\n\").filter(f => f).map(f => path.join(dir, f)).sort();\n}\n\n/** SHA256 hash */\nexport async function hash(content: string|ArrayBufferLike) {\n return crypto.createHash(\"SHA256\")\n .update(typeof content == \"string\" ? content : new Uint8Array(content))\n .digest(\"hex\");\n}\n\n/** Replicate obsidian.normalizePath */\nexport function normalizePath(p: string) {\n p = p.replace(/([\\\\/])+/g, \"/\").replace(/(^\\/+|\\/+$)/g, \"\");\n if (p == \"\") {\n p = \"/\"\n }\n p = p.replace(/\\u00A0|\\u202F/g, \" \"); // replace special space characters\n p = p.normalize(\"NFC\");\n return p;\n}\n\n/** Returns true if a vault file path is hidden (either it or one of it's parent directories starts with \".\") */\nexport function isHidden(file: string) {\n return file.split(\"/\").some(p => p.startsWith(\".\"))\n}\n\n/** Returns true if this is a simple text file */\nexport function isText(file: string) {\n return [\".md\", \".json\", \".txt\", \".js\"].includes(path.extname(file).toLocaleLowerCase());\n}\n","import type * as obsidian from \"obsidian\"\nimport { ObsidianPage } from \"./pageobjects/obsidianPage.js\"\nimport { NormalizedObsidianCapabilityOptions, OBSIDIAN_CAPABILITY_KEY } from \"./types.js\";\nimport { AppInternal } from \"./obsidianTypes.js\";\n\nexport const browserCommands = {\n /**\n * Wrapper around browser.execute that passes the Obsidian API to the function. The first argument to the function\n * is an object containing keys:\n * - app: Obsidian app instance\n * - obsidian: Full Obsidian API\n * - plugins: Object of all installed plugins, mapped by plugin id converted to camelCase.\n * - require: The customized require function Obsidian makes available to plugins. This is also available globally,\n * so you can just use `require` directly instead of from {@link ExecuteObsidianArg} if you prefer.\n * \n * The same caveats as `browser.execute` apply. The function will be stringified and then run inside Obsidian, so\n * you can't capture any local variables. Instead, pass inputs as extra arguments to the function.\n * \n * See also: https://webdriver.io/docs/api/browser/execute\n * \n * Example usage:\n * ```ts\n * const stats = await browser.executeObsidian(({app}, path) => {\n * return app.vault.getMarkdownFiles().find(f => f.path == path)!.stat;\n * }, \"Welcome.md\");\n * ```\n * \n * Make sure not to capture Obsidian types, instead use the provided obsidian module object. E.g. \n * This *won't* work:\n * ```ts\n * import { FileView } from \"obsidian\"\n * browser.executeObsidian(({app}) => {\n * if (leaf.view instanceof FileView) {\n * ...\n * }\n * })\n * ```\n * do this instead:\n * ```ts\n * browser.executeObsidian(({app, obsidian}) => {\n * if (leaf.view instanceof obsidian.FileView) {\n * ...\n * }\n * })\n * ```\n */\n async executeObsidian<Return, Params extends unknown[]>(\n this: WebdriverIO.Browser,\n func: (obs: ExecuteObsidianArg, ...params: Params) => Return,\n ...params: Params\n ): Promise<Return> {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n if (obsidianOptions.vault === undefined) {\n throw Error(\"No vault open, set vault in wdio.conf.mts or use reloadObsidian to open a vault dynamically.\")\n }\n // Call the function. Just stringify the function (browser.execute stringifies it anyways). Add a workaround for\n // node fs errors returning useless error messages. If the thrown error has a \"code\" field that is a string, the\n // chromedriver throws an error like this with no further info:\n // \"unknown error: call function result missing int 'status'\"\n // I think this is a bug in ChromeDevtoolsProtocol or chromedriver. See\n // https://github.com/electron-userland/spectron/issues/1057\n const result = await this.execute<Return, Params>(`\n const require = window.wdioObsidianService().require;\n try {\n return await (\n ${func.toString()}\n ).call(null, window.wdioObsidianService(), ...arguments);\n } catch (e) {\n if (\"code\" in e && typeof e.code != \"number\") {\n delete e.code;\n }\n throw e;\n }\n `, ...params);\n // TODO Should maybe add in the TransformReturn and TransformElement bit that wdio has recently added to the\n // execute types, though it causes weird affects if `func` returns type any.\n return result as Return;\n },\n\n /**\n * Executes an Obsidian command by id.\n * @param id Id of the command to run.\n */\n async executeObsidianCommand(this: WebdriverIO.Browser, id: string) {\n const result = await this.executeObsidian(\n ({app}, id) => (app as AppInternal).commands.executeCommandById(id),\n id,\n );\n if (!result) {\n throw Error(`Obsidian command ${id} not found or failed.`);\n }\n },\n\n /**\n * Returns the Obsidian app version this test is running under.\n */\n getObsidianVersion(this: WebdriverIO.Browser): string {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n return obsidianOptions.appVersion;\n },\n \n /**\n * Returns the Obsidian installer version this test is running under.\n */\n getObsidianInstallerVersion(this: WebdriverIO.Browser): string {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n return obsidianOptions.installerVersion;\n },\n\n /**\n * Returns the ObsidianPage object with convenience helper functions.\n * You can also just import the page object directly with\n * ```ts\n * import { obsidianPage } from \"wdio-obsidian-service\"\n * ```\n */\n getObsidianPage(this: WebdriverIO.Browser): ObsidianPage {\n return new ObsidianPage(this);\n },\n}\n\n/**\n * Extra commands added to the WDIO Browser instance.\n * \n * See also: https://webdriver.io/docs/api/browser\n * @interface\n * @category Utilities\n */\nexport type ObsidianBrowserCommands = typeof browserCommands & {\n /**\n * Relaunch obsidian. Can be used to switch to a new vault, change the plugin list, or just to reboot Obsidian.\n * \n * As this does a full reboot of Obsidian, this is rather slow. In many cases you can use\n * {@link ObsidianPage.resetVault} instead, which modifies vault files in place without rebooting Obsidian. If all\n * your tests use the same vault, you can also just set the vault in the `wdio.conf.mts` capabilities section.\n * \n * @param params.vault Path to the vault to open. The vault will be copied, so any changes made in your tests won't\n * be persited to the original. If omitted, it will reboot Obsidian with the current vault without creating a\n * new copy of the vault.\n * @param params.plugins List of plugin ids to enable. If omitted it will keep current plugin list. Note, all the\n * plugins must be defined in your wdio.conf.mts capabilities. You can also use the enablePlugin and \n * disablePlugin commands to change plugins without relaunching Obsidian.\n * @param params.theme Name of the theme to enable. If omitted it will keep the current theme. Pass \"default\" to\n * switch back to the default theme. Like with plugins, the theme must be defined in wdio.conf.mts.\n */\n reloadObsidian(params?: {\n vault?: string,\n plugins?: string[], theme?: string,\n }): Promise<void>;\n // This command is implemented in the service hooks.\n};\n\n/**\n * Argument passed to the {@link ObsidianBrowserCommands.executeObsidian | executeObsidian} browser command.\n * @category Types\n */\nexport interface ExecuteObsidianArg {\n /**\n * There is a global \"app\" instance, but that may be removed in the future so you can use this to access it from\n * tests. See https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines#Avoid+using+global+app+instance\n */\n app: obsidian.App,\n\n /**\n * The full obsidian API. See https://github.com/obsidianmd/obsidian-api/blob/master/obsidian.d.ts\n */\n obsidian: typeof obsidian,\n\n /**\n * Object containing all installed plugins mapped by their id. Plugin ids are converted to camelCase for ease of\n * destructuring.\n * \n * If you want to add typings for your plugin(s) you can use something like this in a `.d.ts`:\n * ```ts\n * import type MyPlugin from \"../src/main.js\"\n * declare module \"wdio-obsidian-service\" {\n * interface InstalledPlugins {\n * myPlugin: MyPlugin,\n * }\n * }\n * ```\n */\n plugins: InstalledPlugins,\n\n /**\n * The customized require function Obsidian makes available to plugins. This is also available globally, so you can\n * just use `require` directly instead of from {@link ExecuteObsidianArg} if you prefer.\n */\n require: NodeJS.Require,\n}\n\n/**\n * Installed plugins, mapped by their id converted to camelCase\n * @category Types\n */\nexport interface InstalledPlugins extends Record<string, obsidian.Plugin> {\n}\n","import { remote } from 'webdriverio'\nimport type { Capabilities, Options } from '@wdio/types'\nimport { ObsidianLauncherService, ObsidianWorkerService } from \"./service.js\"\nimport { ObsidianServiceOptions } from \"./types.js\"\n\n/**\n * Starts an Obsidian instance for WDIO standalone mode.\n * \n * For testing, you'll usually want to use the WDIO testrunner with Mocha, wdio.conf.mts, etc. to launch WDIO. However\n * if you want to use WDIO for some kind of scripting scenario, you can use this function to launch a WDIO standalone\n * session connected to Obsidian.\n * \n * See also: https://webdriver.io/docs/setuptypes/#standalone-mode\n * \n * Example:\n * ```ts\n * const browser = await startWdioSession({\n * capabilities: {\n * browserName: \"obsidian\",\n * browserVersion: \"latest\",\n * 'wdio:obsidianOptions': {\n * installerVersion: \"latest\",\n * vault: \"./test/vaults/basic\",\n * },\n * },\n * });\n * await browser.executeObsidian(({app}) => {\n * // extract some file metadata, edit the vault, etc...\n * });\n * await browser.deleteSession();\n * ```\n * \n * Note that in standalone mode, the global `obsidianPage` instance won't work, you have to use\n * {@link ObsidianBrowserCommands.getObsidianPage | getObsidianPage} to get the page object, e.g.:\n * ```js\n * const obsidianPage = browser.getObsidianPage()\n * ```\n * instead of the \n * @category WDIO Helpers\n */\nexport async function startWdioSession(\n params: Capabilities.WebdriverIOConfig,\n serviceOptions?: ObsidianServiceOptions,\n): Promise<WebdriverIO.Browser> {\n serviceOptions = serviceOptions ?? {};\n const capabilities = params.capabilities as WebdriverIO.Capabilities;\n const testRunnerOptions: Options.Testrunner = {\n cacheDir: params.cacheDir,\n };\n const launcherService = new ObsidianLauncherService(\n serviceOptions,\n [capabilities] as WebdriverIO.Capabilities,\n testRunnerOptions,\n );\n const workerService = new ObsidianWorkerService(serviceOptions, capabilities, testRunnerOptions);\n\n await launcherService.onPrepare(testRunnerOptions, [capabilities]);\n await workerService.beforeSession(testRunnerOptions, capabilities);\n\n const browser = await remote(params);\n\n await workerService.before(capabilities, [], browser);\n\n return browser;\n}\n"],"mappings":"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","names":["ObsidianLauncher","fsAsync","path","fileURLToPath","path","fsAsync","crypto","_","browser","path","browser","data","pluginId","themeName","path","workspacesPath","layout","file","strContent","binContent","parent","_","configDir","id","_","path","fileURLToPath","browser","androidVault","width","height","resolve","remote","fsAsync","browser","launcher","ObsidianLauncher"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/service.ts","../src/pageobjects/obsidianPage.ts","../src/types.ts","../../../node_modules/.pnpm/@wdio+globals@9.17.0_expect-webdriverio@5.6.4_webdriverio@9.18.1/node_modules/@wdio/globals/build/index.js","../src/pageobjects/basePage.ts","../src/utils.ts","../src/browserCommands.ts","../src/standalone.ts"],"sourcesContent":["/**\n * @module\n * @document ../README.md\n * @categoryDescription Options\n * Capability and service options.\n * @categoryDescription WDIO Helpers\n * Helpers for use in wdio.conf.mts, or for launching WDIO in standalone mode.\n * @categoryDescription Utilities\n * Browser commands and helper functions for writing tests.\n */\nimport ObsidianLauncher from \"obsidian-launcher\";\nimport { deprecate } from \"util\";\n\nimport { ObsidianLauncherService, ObsidianWorkerService } from \"./service.js\";\n/** @hidden */\nexport default ObsidianWorkerService;\n/** @hidden */\nexport const launcher = ObsidianLauncherService;\n\nexport type { ObsidianCapabilityOptions, ObsidianServiceOptions } from \"./types.js\";\nexport type { ObsidianBrowserCommands, ExecuteObsidianArg, InstalledPlugins } from \"./browserCommands.js\";\nexport { default as obsidianPage } from \"./pageobjects/obsidianPage.js\";\nexport type { ObsidianPage, Platform } from \"./pageobjects/obsidianPage.js\";\nexport type { PluginEntry, ThemeEntry } from \"obsidian-launcher\";\n\nexport { minSupportedObsidianVersion } from \"./service.js\";\nexport { startWdioSession } from \"./standalone.js\";\n\n// Some convenience helpers for use in wdio.conf.mts\n\n/**\n * Returns true if there is a current Obsidian beta and we have the credentials to download it, or its already in cache.\n * @category WDIO Helpers\n * @param opts.cacheDir Obsidian cache dir, defaults to `.obsidian-cache`.\n */\nexport async function obsidianBetaAvailable(opts: string|{cacheDir?: string} = {}) {\n opts = typeof opts == \"string\" ? {cacheDir: opts} : opts;\n const launcher = new ObsidianLauncher(opts);\n const versionInfo = await launcher.getVersionInfo(\"latest-beta\");\n return versionInfo.isBeta && await launcher.isAvailable(versionInfo.version);\n}\n\n/**\n * Resolves Obsidian app and installer version strings to absolute versions.\n * \n * @category WDIO Helpers\n * @deprecated Use parseObsidianVersions instead\n */\nexport const resolveObsidianVersions = deprecate(async function(\n appVersion: string, installerVersion: string, cacheDir?: string,\n): Promise<[string, string]> {\n const launcher = new ObsidianLauncher({cacheDir: cacheDir});\n return await launcher.resolveVersion(appVersion, installerVersion);\n}, 'resolveObsidianVersions is deprecated, use parseObsidianVersions instead');\n\n\n/**\n * Parses a string of Obsidian versions into [appVersion, installerVersion] tuples. This is a convenience helper for use\n * in `wdio.conf.mts`\n * \n * `versions` should be a space separated list of Obsidian app versions. You can optionally specify the installer\n * version by using \"appVersion/installerVersion\" e.g. `\"1.7.7/1.8.10\"`.\n * \n * Example: \n * ```js\n * parseObsidianVersions(\"1.8.10/1.7.7 latest latest-beta/earliest\")\n * ```\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n * \n * @category WDIO Helpers\n * @param versions string to parse\n * @param opts.cacheDir Obsidian cache dir, defaults to `.obsidian-cache`.\n * @returns [appVersion, installerVersion][] resolved to specific versions.\n */\nexport async function parseObsidianVersions(\n versions: string,\n opts: {cacheDir?: string} = {},\n): Promise<[string, string][]> {\n const launcher = new ObsidianLauncher({cacheDir: opts.cacheDir});\n return launcher.parseVersions(versions);\n}\n","import fs from \"fs\"\nimport fsAsync from \"fs/promises\"\nimport path from \"path\"\nimport { SevereServiceError } from 'webdriverio'\nimport type { Capabilities, Options, Services } from '@wdio/types'\nimport logger from '@wdio/logger'\nimport { fileURLToPath } from \"url\"\nimport ObsidianLauncher, {\n PluginEntry, ThemeEntry, DownloadedPluginEntry, DownloadedThemeEntry,\n} from \"obsidian-launcher\"\nimport { browserCommands } from \"./browserCommands.js\"\nimport {\n ObsidianServiceOptions, NormalizedObsidianCapabilityOptions, OBSIDIAN_CAPABILITY_KEY,\n} from \"./types.js\"\nimport {\n isAppium, appiumUploadFiles, appiumDownloadFile, getAppiumOptions, fileExists, navigateAndWait,\n} from \"./utils.js\";\nimport semver from \"semver\"\nimport _ from \"lodash\"\n\n\nconst log = logger(\"wdio-obsidian-service\");\n\nfunction getDefaultCacheDir(rootDir: string) {\n return path.resolve(rootDir, process.env.WEBDRIVER_CACHE_DIR ?? process.env.OBSIDIAN_CACHE ?? \"./.obsidian-cache\");\n}\n\n/** By default wdio continues on service errors, so we throw a SevereServiceError to make it bail on error */\nfunction getServiceErrorMessage(e: any) {\n return (\n `Failed to download and setup Obsidian. Caused by:\\n` +\n `${e.stack}\\n`+\n ` ------The above causes:-----`\n );\n}\n\nfunction resolveEntry(rootDir: string, entry: PluginEntry|ThemeEntry): PluginEntry|ThemeEntry {\n if (typeof entry == \"string\") {\n return path.resolve(rootDir, entry);\n } else if ('path' in entry) {\n return {...entry, path: path.resolve(rootDir, entry.path)};\n } else {\n return entry;\n }\n}\n\n/** Returns a plugin list with only the selected plugin ids enabled. */\nfunction selectPlugins(currentPlugins: DownloadedPluginEntry[], selection?: string[]): DownloadedPluginEntry[] {\n if (selection !== undefined) {\n const unknownPlugins = _.difference(selection, currentPlugins.map(p => p.id));\n if (unknownPlugins.length > 0) {\n throw Error(`Unknown plugin ids: ${unknownPlugins.join(', ')}`)\n }\n return currentPlugins.map(p => ({\n ...p,\n enabled: selection.includes(p.id) || p.id == \"wdio-obsidian-service-plugin\",\n }));\n } else {\n return currentPlugins;\n }\n}\n\n/** Returns a theme list with only the selected theme enabled. */\nfunction selectThemes(currentThemes: DownloadedThemeEntry[], selection?: string): DownloadedThemeEntry[] {\n if (selection !== undefined) {\n if (selection != \"default\" && currentThemes.every(t => t.name != selection)) {\n throw Error(`Unknown theme: ${selection}`);\n }\n return currentThemes.map(t => ({...t, enabled: selection != 'default' && t.name === selection}));\n } else {\n return currentThemes;\n }\n}\n\nfunction getNormalizedObsidianOptions(cap: WebdriverIO.Capabilities): NormalizedObsidianCapabilityOptions {\n return cap[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n}\n\n\n/**\n * Minimum Obsidian version that wdio-obsidian-service supports.\n * @category WDIO Helpers\n */\nexport const minSupportedObsidianVersion: string = \"1.0.3\"\n\n\n/**\n * wdio launcher service.\n * Use in wdio.conf.mts like so:\n * ```ts\n * services: ['obsidian'],\n * ```\n * @hidden\n */\nexport class ObsidianLauncherService implements Services.ServiceInstance {\n private obsidianLauncher: ObsidianLauncher\n private readonly helperPluginPath: string\n private readonly rootDir: string\n\n constructor (\n public options: ObsidianServiceOptions,\n public capabilities: WebdriverIO.Capabilities,\n public config: Options.Testrunner\n ) {\n this.rootDir = config.rootDir || process.cwd();\n this.obsidianLauncher = new ObsidianLauncher({\n cacheDir: config.cacheDir ?? getDefaultCacheDir(this.rootDir),\n versionsUrl: options.versionsUrl,\n communityPluginsUrl: options.communityPluginsUrl, communityThemesUrl: options.communityThemesUrl,\n });\n this.helperPluginPath = path.resolve(fileURLToPath(import.meta.url), '../../helper-plugin');\n }\n\n /**\n * Validates wdio:obsidianOptions and downloads Obsidian, plugins, and themes.\n */\n async onPrepare(config: Options.Testrunner, capabilities: Capabilities.TestrunnerCapabilities) {\n try {\n if (!Array.isArray(capabilities)) {\n capabilities = Object.values(capabilities).map(\n (multiremoteOption) => (multiremoteOption as Capabilities.WithRequestedCapabilities).capabilities,\n );\n }\n\n const obsidianCapabilities = capabilities.flatMap((cap) => {\n if ((\"browserName\" in cap) && cap.browserName === \"obsidian\") {\n return [cap as WebdriverIO.Capabilities];\n } else {\n return [];\n }\n });\n\n for (const cap of obsidianCapabilities) {\n const obsidianOptions = cap[OBSIDIAN_CAPABILITY_KEY] ?? {};\n \n // check vault\n const vault = obsidianOptions.vault ? path.resolve(this.rootDir, obsidianOptions.vault) : undefined;\n if (vault && !fs.existsSync(vault)) {\n throw Error(`Vault \"${vault}\" doesn't exist`)\n }\n \n // download plugins and themes to cache\n const plugins = await this.obsidianLauncher.downloadPlugins(\n (obsidianOptions.plugins ?? [])\n .concat([this.helperPluginPath]) // Always install the helper plugin\n .map(p => resolveEntry(this.rootDir, p) as PluginEntry)\n );\n\n const themes = await this.obsidianLauncher.downloadThemes(\n (obsidianOptions.themes ?? [])\n .map(t => resolveEntry(this.rootDir, t) as ThemeEntry),\n );\n\n let appVersion = cap.browserVersion ?? cap[OBSIDIAN_CAPABILITY_KEY]?.appVersion ?? \"latest\";\n appVersion = (await this.obsidianLauncher.getVersionInfo(appVersion)).version;\n if (semver.lt(appVersion, minSupportedObsidianVersion)) {\n throw Error(`Minimum supported Obsidian version is ${minSupportedObsidianVersion}`)\n }\n\n if (isAppium(cap)) {\n let apk = getAppiumOptions(cap).app;\n if (!apk) {\n apk = await this.obsidianLauncher.downloadAndroid(appVersion);\n }\n let chromedriverDir = getAppiumOptions(cap).chromedriverExecutableDir;\n if (!chromedriverDir) {\n chromedriverDir = path.join(this.obsidianLauncher.cacheDir, 'appium-chromedriver');\n }\n\n const normalizedObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...obsidianOptions,\n plugins, themes, vault: vault,\n appVersion, installerVersion: appVersion,\n emulateMobile: false,\n }\n cap[OBSIDIAN_CAPABILITY_KEY] = normalizedObsidianOptions;\n cap['appium:app'] = apk;\n cap['appium:chromedriverExecutableDir'] = chromedriverDir;\n cap[\"wdio:enforceWebDriverClassic\"] = true; // BiDi doesn't seem to work on Obsidian mobile\n // appium-service expects these to not be set\n delete cap.browserName;\n delete cap.browserVersion;\n if (!getAppiumOptions(cap)['noReset']) {\n console.warn(\"Note: For best performance set noReset to true, wdio-obsidian-service will handle resetting Obsidian between tests.\")\n }\n } else {\n const [, installerVersion] = await this.obsidianLauncher.resolveVersion(\n appVersion, obsidianOptions.installerVersion ?? \"earliest\",\n );\n const installerInfo = await this.obsidianLauncher.getInstallerInfo(installerVersion);\n\n let installerPath: string;\n if (obsidianOptions.binaryPath) {\n installerPath = path.resolve(this.rootDir, obsidianOptions.binaryPath)\n } else {\n installerPath = await this.obsidianLauncher.downloadInstaller(installerVersion);\n }\n let appPath: string;\n if (obsidianOptions.appPath) {\n appPath = path.resolve(this.rootDir, obsidianOptions.appPath)\n } else {\n appPath = await this.obsidianLauncher.downloadApp(appVersion);\n }\n let chromedriverPath = cap['wdio:chromedriverOptions']?.binary;\n // wdio can't download chromedriver for versions less than 115 automatically. Fetching it ourselves is\n // also a bit faster as it skips the chromedriver version detection step.\n if (!chromedriverPath) {\n chromedriverPath = await this.obsidianLauncher.downloadChromedriver(installerVersion);\n }\n\n const normalizedObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...obsidianOptions,\n plugins, themes, vault,\n binaryPath: installerPath, appPath: appPath,\n appVersion, installerVersion,\n emulateMobile: obsidianOptions.emulateMobile ?? false,\n }\n\n cap.browserName = \"chrome\";\n cap.browserVersion = installerInfo.chrome;\n cap[OBSIDIAN_CAPABILITY_KEY] = normalizedObsidianOptions;\n cap['goog:chromeOptions'] = {\n binary: installerPath,\n windowTypes: [\"app\", \"webview\"],\n ...cap['goog:chromeOptions'],\n args: [\n // Workaround for SUID issue on linux. See https://github.com/electron/electron/issues/42510\n ...(process.platform == 'linux' ? [\"--no-sandbox\"] : []),\n ...(cap['goog:chromeOptions']?.args ?? [])\n ],\n }\n cap['wdio:chromedriverOptions'] = {\n // allowedIps is not included in the types, but gets passed as --allowed-ips to chromedriver.\n // It defaults to [\"0.0.0.0\"] which makes Windows Firewall complain, and we don't need remote\n // connections anyways.\n allowedIps: [],\n ...cap['wdio:chromedriverOptions'],\n binary: chromedriverPath,\n } as WebdriverIO.ChromedriverOptions;\n cap[\"wdio:enforceWebDriverClassic\"] = true; // electron doesn't support BiDi yet.\n }\n }\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n}\n\n\n/**\n * wdio worker service.\n * Use in wdio.conf.mts like so:\n * ```ts\n * services: ['obsidian'],\n * ```\n * @hidden\n */\nexport class ObsidianWorkerService implements Services.ServiceInstance {\n private obsidianLauncher: ObsidianLauncher\n private browser: WebdriverIO.Browser|undefined;\n /** Directories to clean up after the tests */\n private tmpDirs: string[]\n /** Path on Android devices to store temporary vaults */\n private androidVaultDir = \"/storage/emulated/0/Documents/wdio-obsidian-service-vaults\";\n\n constructor (\n public options: ObsidianServiceOptions,\n public capabilities: WebdriverIO.Capabilities,\n public config: Options.Testrunner\n ) {\n this.obsidianLauncher = new ObsidianLauncher({\n cacheDir: config.cacheDir ?? getDefaultCacheDir(config.rootDir || process.cwd()),\n versionsUrl: options.versionsUrl,\n communityPluginsUrl: options.communityPluginsUrl, communityThemesUrl: options.communityThemesUrl,\n });\n this.tmpDirs = [];\n }\n\n /**\n * Creates a copy of the vault with plugins and themes installed\n */\n private async setupVault(cap: WebdriverIO.Capabilities) {\n const obsidianOptions = getNormalizedObsidianOptions(cap);\n let vaultCopy: string|undefined;\n if (obsidianOptions.vault != undefined) {\n log.info(`Opening vault ${obsidianOptions.vault}`);\n vaultCopy = await this.obsidianLauncher.setupVault({\n vault: obsidianOptions.vault,\n copy: true,\n plugins: obsidianOptions.plugins,\n themes: obsidianOptions.themes,\n });\n this.tmpDirs.push(vaultCopy);\n } else {\n log.info(`Opening Obsidian without a vault`)\n }\n obsidianOptions.vaultCopy = vaultCopy; // for use in getVaultPath() and the other service hooks\n }\n\n /**\n * Sets up the --user-data-dir for the Electron app. Sets the obsidian.json in the dir to open the vault on boot.\n */\n private async electronSetupConfigDir(cap: WebdriverIO.Capabilities) {\n const obsidianOptions = getNormalizedObsidianOptions(cap);\n const configDir = await this.obsidianLauncher.setupConfigDir({\n appVersion: obsidianOptions.appVersion, installerVersion: obsidianOptions.installerVersion,\n appPath: obsidianOptions.appPath,\n vault: obsidianOptions.vaultCopy,\n // `app.emulateMobile` just sets this localStorage variable. Setting it ourselves here instead of calling\n // the function simplifies the boot/plugin load sequence and makes sure plugins load in mobile mode.\n localStorage: obsidianOptions.emulateMobile ? {\"EmulateMobile\": \"1\"} : {},\n });\n this.tmpDirs.push(configDir);\n\n cap['goog:chromeOptions'] = {\n ...cap['goog:chromeOptions'],\n args: [\n `--user-data-dir=${configDir}`,\n ...(cap['goog:chromeOptions']?.args ?? []).filter(arg => {\n const match = arg.match(/^--user-data-dir=(.*)$/);\n return !match || !this.tmpDirs.includes(match[1]);\n })\n ]\n }\n }\n\n /**\n * Sets up the Obsidian app. Installs and launches it if needed, and sets permissions and contexts.\n * \n * You can configure Appium to install and launch the app for you. However, if you do that it reboots the app after\n * every session/spec which is really slow. So we are handling the app setup manually here.\n */\n private async appiumSetupApp() {\n const browser = this.browser!;\n const appiumOptions = getAppiumOptions(browser.requestedCapabilities);\n const obsidianOptions = getNormalizedObsidianOptions(browser.requestedCapabilities);\n const appId = \"md.obsidian\";\n\n // install/reinstall Obsidian if needed\n const dumpsys: string = await browser.execute(\"mobile: shell\", {command: \"dumpsys\", args: [\"package\", appId]});\n const installedObsidianVersion = dumpsys.match(/versionName[=:](.*)$/m)?.[1]?.trim();\n if (installedObsidianVersion != obsidianOptions.appVersion) {\n await browser.execute('mobile: terminateApp', {appId}); // terminate app (if running)\n await browser.execute('mobile: removeApp', {appId, keepData: false}); // uninstall (if present)\n await browser.execute('mobile: installApp', {\n appPath: appiumOptions.app, // the APK\n timeout: appiumOptions.androidInstallTimeout, // respect appium configuration\n grantPermissions: true, // this and autoGrantPermissions don't seem to really work, see below\n });\n }\n\n // start app if needed\n const appState: number = await browser.execute(\"mobile: queryAppState\", {appId});\n if (appState < 4) { // 0: not installed, 1: not running, 3: running in background, 4: running in foreground\n await browser.execute(\"mobile: activateApp\", {appId});\n }\n\n // grant Obsidian the permissions it needs, mainly file access.\n // appium:autoGrantPermissions is supposed to automatically grant everything it needs, but I can't get it to\n // work. The \"mobile: changePermissions\" \"all\" option also doesn't seem to work here. I have to explicitly list\n // the permissions and use the \"appops\" target. See https://github.com/appium/appium/issues/19991\n // I'm calling \"mobile: changePermissions\" \"all\" as well just in case it is actually doing something\n await browser.execute(\"mobile: changePermissions\", {\n action: \"grant\",\n appPackage: appId,\n permissions: \"all\",\n });\n await browser.execute(\"mobile: changePermissions\", {\n action: \"allow\",\n appPackage: appId,\n permissions: [ // these are from apk AndroidManifest.xml (extracted with apktool)\n \"READ_EXTERNAL_STORAGE\", \"WRITE_EXTERNAL_STORAGE\", \"MANAGE_EXTERNAL_STORAGE\",\n ],\n target: \"appops\", // requires appium --allow-insecure adb_shell\n });\n\n // switch to the webview context\n const context = \"WEBVIEW_md.obsidian\";\n await browser.waitUntil(\n async () => (await browser.getContexts() as string[]).includes(context),\n {timeout: 30_000, interval: 100},\n );\n await browser.switchContext(context);\n\n // Clear app state\n await browser.execute(() => { // in case tests get interrupted during the `_capacitor_file_` bit\n if (window.location.href != \"http://localhost/\") {\n window.location.replace('http://localhost/');\n }\n })\n if (!obsidianOptions.vault) { // skip if vault is set, as we'll clear state when we open the new vault\n await this.appiumCloseVault();\n }\n }\n\n /**\n * Opens the vault in appium.\n */\n private async appiumOpenVault() {\n const browser = this.browser!;\n const obsidianOptions = getNormalizedObsidianOptions(browser.requestedCapabilities);\n const androidVault = `${this.androidVaultDir}/${path.basename(obsidianOptions.vaultCopy!)}`;\n // TODO: Capabilities is not really the right place to be storing state like vaultCopy and uploadVault\n obsidianOptions.uploadedVault = androidVault;\n // transfer the vault to the device\n await appiumUploadFiles(browser, {src: obsidianOptions.vaultCopy!, dest: androidVault});\n\n // open vault by setting the localStorage keys and relaunching Obsidian\n // on appium restarting the app with appium:fullReset is *really* slow. And, unlike electron we can actually\n // switch vault with just a reload. So for Appium, instead of rebooting we manually wipe localStorage and use\n // reload to switch the vault.\n await browser.execute(async (androidVault) => {\n localStorage.clear();\n localStorage.setItem('mobile-external-vaults', JSON.stringify([androidVault]));\n localStorage.setItem('mobile-selected-vault', androidVault);\n // appId on mobile is just the full vault path\n localStorage.setItem(`enable-plugin-${androidVault}`, 'true');\n }, androidVault);\n await navigateAndWait(browser, () => location.reload());\n }\n\n /**\n * Close any open vault and go back to the vault switcher\n */\n private async appiumCloseVault() {\n const browser = this.browser!;\n // skip if we're already on the vault switcher\n if (await browser.execute(() => localStorage.length > 0)) {\n await browser.execute(() => localStorage.clear());\n await navigateAndWait(browser, () => location.reload());\n }\n }\n\n /**\n * Waits for Obsidian to be ready, and does some other final setup.\n */\n private async prepareApp() {\n const browser = this.browser!;\n const obsidianOptions = getNormalizedObsidianOptions(browser.requestedCapabilities);\n\n if (obsidianOptions.emulateMobile && obsidianOptions.vault != undefined) {\n // I don't think this is technically necessary, but when you set the window size via emulateMobile it sets\n // the window size in the browser view, but not the actual window size which looks weird and makes it hard\n // to manually debug a paused test. The normal ways to set window size don't work on Obsidian. Obsidian\n // doesn't respect the `--window-size` argument, and wdio setViewport and setWindowSize don't work without\n // BiDi. This resizes the window directly using electron APIs.\n await browser.waitUntil(\n () => browser.execute(() => !!(window as any).electron),\n {timeout: 10_000, interval: 100},\n );\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: 60_000, interval: 100},\n );\n await browser.executeObsidian(async ({app}) => {\n await new Promise<void>((resolve) => app.workspace.onLayoutReady(resolve));\n });\n } else {\n await browser.execute(async () => {\n if (document.readyState === \"loading\") {\n return new Promise<void>(resolve => document.addEventListener(\"DOMContentLoaded\", () => resolve()));\n }\n });\n }\n }\n\n private createReloadObsidian() {\n const service = this; // eslint-disable-line @typescript-eslint/no-this-alias\n const reloadObsidian: WebdriverIO.Browser['reloadObsidian'] = async function(\n this: WebdriverIO.Browser,\n {vault, plugins, theme} = {},\n ) {\n const oldObsidianOptions = getNormalizedObsidianOptions(this.requestedCapabilities);\n const selectedPlugins = selectPlugins(oldObsidianOptions.plugins, plugins);\n const selectedThemes = selectThemes(oldObsidianOptions.themes, theme);\n if (!vault && oldObsidianOptions.vaultCopy == undefined) {\n throw Error(`No vault is open, pass a vault path to reloadObsidian`);\n }\n const newObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...oldObsidianOptions,\n // Resolve relative to PWD instead of root dir during tests\n vault: vault ? path.resolve(vault) : oldObsidianOptions.vault,\n plugins: selectedPlugins, themes: selectedThemes,\n };\n\n if (isAppium(this.requestedCapabilities)) {\n this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] = newObsidianOptions;\n if (vault) {\n await service.setupVault(this.requestedCapabilities);\n await service.appiumOpenVault();\n } else {\n // reload without resetting app state or triggering appium:fullReset\n\n // hack to disable the Obsidian app and make sure it doesn't write to the vault while we modify it\n await navigateAndWait(this, () => location.replace('http://localhost/_capacitor_file_/not-a-file'));\n\n // while Obsidian is down, modify the vault files to setup plugins and themes\n const local = path.join(oldObsidianOptions.vaultCopy!, \".obsidian\");\n const localCommunityPlugins = path.join(local, \"community-plugins.json\");\n const localAppearance = path.join(local, \"appearance.json\");\n const remote = `${oldObsidianOptions.uploadedVault!}/.obsidian`;\n const remoteCommunityPlugins = `${remote}/community-plugins.json`;\n const remoteAppearance = `${remote}/appearance.json`;\n\n await appiumDownloadFile(this, remoteCommunityPlugins, localCommunityPlugins).catch(() => {});\n await appiumDownloadFile(this, remoteAppearance, localAppearance).catch(() => {});\n await service.obsidianLauncher.setupVault({\n vault: oldObsidianOptions.vaultCopy!, copy: false,\n plugins: selectedPlugins, themes: selectedThemes,\n });\n let files = [localCommunityPlugins, localAppearance];\n files = (await Promise.all(files.map(async f => await fileExists(f) ? f : \"\"))).filter(f => f);\n await appiumUploadFiles(this, {src: local, dest: remote, files});\n\n // switch the app back\n await navigateAndWait(this, () => location.replace('http://localhost/'));\n }\n } else {\n // if browserName is set, reloadSession tries to restart the driver entirely, so unset those\n const newCap: WebdriverIO.Capabilities = _.cloneDeep(\n _.omit(this.requestedCapabilities, ['browserName', 'browserVersion'])\n );\n newCap[OBSIDIAN_CAPABILITY_KEY] = newObsidianOptions;\n\n if (vault) {\n await service.setupVault(newCap);\n await service.electronSetupConfigDir(newCap);\n await this.reloadSession(newCap);\n } else {\n // reload preserving current vault and config dir\n\n // Obsidian debounces saves to the config dir, and so changes to configuration made in the tests may\n // not get saved to disk before the reboot. I haven't found a better way to flush everything than\n // just waiting a bit.\n await this.pause(2000);\n\n await this.deleteSession({shutdownDriver: false});\n // while Obsidian is down, modify the vault files to setup plugins and themes\n await service.obsidianLauncher.setupVault({\n vault: oldObsidianOptions.vaultCopy!, copy: false,\n plugins: selectedPlugins, themes: selectedThemes,\n });\n await this.reloadSession(newCap);\n }\n }\n await service.prepareApp();\n };\n return reloadObsidian;\n }\n\n /**\n * Runs before the session and browser have started.\n */\n async beforeSession(config: Options.Testrunner, cap: WebdriverIO.Capabilities) {\n try {\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n if (cap[OBSIDIAN_CAPABILITY_KEY].vault != undefined) {\n await this.setupVault(cap);\n }\n if (!isAppium(cap)) {\n await this.electronSetupConfigDir(cap);\n }\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n\n /**\n * Runs after session and browser have started, but before tests.\n */\n async before(cap: WebdriverIO.Capabilities, specs: unknown, browser: WebdriverIO.Browser) {\n this.browser = browser;\n try {\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n\n // You are supposed to add commands via the addCommand hook, however you can't add synchronous methods that\n // way. Also, addCommand completely breaks the stack traces of errors from the methods, while tacking on the\n // methods manually doesn't.\n const newBrowserCommands = {\n ...browserCommands,\n reloadObsidian: this.createReloadObsidian(),\n }\n for (const [name, cmd] of Object.entries(newBrowserCommands)) {\n (browser as any)[name] = cmd;\n }\n\n if (isAppium(browser.requestedCapabilities)) {\n await this.appiumSetupApp();\n if (cap[OBSIDIAN_CAPABILITY_KEY].vault) {\n await this.appiumOpenVault();\n }\n }\n await this.prepareApp();\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n\n /** Runs after tests are done, but before the session is shut down */\n async after(result: number, cap: WebdriverIO.Capabilities) {\n const browser = this.browser!;\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n\n if (isAppium(cap)) {\n await this.appiumCloseVault();\n // \"mobile: deleteFile\" doesn't work on folders. \"mobile:shell\" requires appium --allow-insecure adb_shell\n await browser.execute(\"mobile: shell\", {command: \"rm\", args: [\"-rf\", this.androidVaultDir]});\n }\n }\n\n /**\n * Cleanup\n */\n async afterSession() {\n for (const tmpDir of this.tmpDirs) {\n await fsAsync.rm(tmpDir, { recursive: true, force: true });\n }\n }\n}\n","import * as path from \"path\"\nimport * as fsAsync from \"fs/promises\"\nimport * as crypto from \"crypto\";\nimport { promisify } from \"util\";\nimport child_process from \"child_process\";\nimport * as semver from \"semver\";\nimport { fileURLToPath } from \"url\";\nimport { TFile } from \"obsidian\";\nimport _ from \"lodash\";\nimport { OBSIDIAN_CAPABILITY_KEY, NormalizedObsidianCapabilityOptions } from \"../types.js\";\nimport { BasePage } from \"./basePage.js\";\nimport { isAppium, normalizePath, isHidden, isText } from \"../utils.js\";\nimport { AppInternal, PlatformInternal } from \"../obsidianTypes.js\";\nconst execFile = promisify(child_process.execFile);\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 * Run an Obsidian CLI command.\n * \n * The Obsidian CLI Only works on Obsidian >=1.12.0 with installer >=1.11.7\n * See https://help.obsidian.md/cli\n */\n async runObsidianCli(args: string[]): Promise<{stdout: string, stderr: string}> {\n const {appVersion, installerVersion, binaryPath} = this.getObsidianCapabilities();\n if (semver.lt(appVersion, \"1.12.0\") || semver.lt(installerVersion, \"1.11.7\")) {\n throw Error(`Obsidian CLI only works on app >=1.12.0 and installer >=1.11.7`)\n }\n if (isAppium(this.browser.requestedCapabilities)) {\n throw Error(`Obsidian CLI only works on desktop`)\n }\n const chromeArgs: string[] = this.browser.requestedCapabilities['goog:chromeOptions'].args;\n const cliBinary = binaryPath!.replace(/.exe$/, '.com'); // Windows uses the .com wrapper\n return await execFile(cliBinary, [...args, ...chromeArgs]);\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 const appium = isAppium(this.browser.requestedCapabilities);\n const emulateMobile = obsidianOptions.emulateMobile;\n\n // To allow calling getPlatform before opening a vault, we'll compute the values ourselves when a vault isn't\n // open. We'll pull obsidian Platform if there is a vault, as technically you can change the screen size and\n // emulation mode during tests. Also, on very old versions of Obsidian, not all these fields are exist.\n const platform = {\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 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 if (obsidianOptions.vault !== undefined) {\n const obsidianPlatform = await this.browser.executeObsidian(({obsidian}) => {\n // The types for this in old versions of obsidian types are incomplete, so any cast here\n const p = obsidian.Platform as PlatformInternal;\n return {\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 Object.assign(platform, _.pickBy(obsidianPlatform, x => x != null));\n } else {\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 Object.assign(platform, {isTablet, isPhone});\n }\n\n return platform as Platform;\n }\n\n /**\n * Enables a plugin by ID\n */\n async enablePlugin(pluginId: string): Promise<void> {\n await this.browser.executeObsidian(\n async ({app}, pluginId) => await (app as AppInternal).plugins.enablePluginAndSave(pluginId),\n pluginId,\n );\n }\n\n /**\n * Disables a plugin by ID\n */\n async disablePlugin(pluginId: string): Promise<void> {\n await this.browser.executeObsidian(\n async ({app}, pluginId) => await (app as AppInternal).plugins.disablePluginAndSave(pluginId),\n pluginId,\n );\n }\n\n /**\n * Sets the theme. Pass \"default\" to reset to the Obsidian theme.\n */\n async setTheme(themeName: string): Promise<void> {\n themeName = themeName == 'default' ? '' : themeName;\n await this.browser.executeObsidian(\n async ({app}, themeName) => await (app as AppInternal).customCss.setTheme(themeName),\n themeName,\n )\n }\n\n /**\n * Opens a file in a new tab.\n */\n async openFile(path: string) {\n await this.browser.executeObsidian(async ({app, obsidian}, path) => {\n const file = app.vault.getAbstractFileByPath(path);\n if (file instanceof obsidian.TFile) {\n const leaf = app.workspace.getLeaf('tab');\n await leaf.openFile(file);\n app.workspace.setActiveLeaf(leaf, {focus: true});\n } else {\n throw Error(`No file ${path} exists`);\n }\n }, path)\n }\n\n /**\n * Loads a saved workspace layout from `.obsidian/workspaces.json` by name. Use the core \"Workspaces\"\n * plugin to create the layouts. You can also pass the layout object directly.\n */\n async loadWorkspaceLayout(layout: any): Promise<void> {\n if (typeof layout == \"string\") {\n // read from .obsidian/workspaces.json like the built-in workspaces plugin does\n const workspacesPath = `${await this.getConfigDir()}/workspaces.json`;\n const layoutName = layout;\n try {\n const fileContent = await this.browser.executeObsidian(async ({app}, workspacesPath) => {\n return await app.vault.adapter.read(workspacesPath);\n }, workspacesPath);\n layout = JSON.parse(fileContent)?.workspaces?.[layoutName];\n } catch {\n throw new Error(`Failed to load ${workspacesPath}:${layoutName}`);\n }\n if (!layout) {\n throw new Error(`No workspace ${layoutName} found in ${workspacesPath}`);\n }\n }\n\n await this.browser.executeObsidian(async ({app}, layout) => {\n await app.workspace.changeLayout(layout)\n }, layout)\n }\n\n /**\n * Return contents of a file in the vault.\n */\n async read(file: string): Promise<string> {\n return await this.browser.executeObsidian(async ({app, obsidian}, file) => {\n file = obsidian.normalizePath(file);\n return await app.vault.adapter.read(file);\n }, file);\n }\n\n /**\n * Return contents of a binary file in the vault.\n */\n async readBinary(file: string): Promise<ArrayBuffer> {\n const b64Contents = await this.browser.executeObsidian(async ({app, obsidian}, file) => {\n function toB64(buffer: ArrayBuffer) {\n let binary = ''\n const bytes = new Uint8Array(buffer)\n const len = bytes.byteLength\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(bytes[i])\n }\n return btoa(binary)\n }\n\n file = obsidian.normalizePath(file);\n return toB64(await app.vault.adapter.readBinary(file));\n }, file);\n return Buffer.from(b64Contents, 'base64').buffer;\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|ArrayBufferLike|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|ArrayBufferLike|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 = {\n type: \"file\"|\"folder\",\n sourceContent?: string|ArrayBufferLike|ArrayBuffer,\n sourceFile?: string,\n };\n const newVault: Map<string, NewFileInfo> = new Map();\n for (let vault of vaults) {\n if (typeof vault == \"string\") {\n vault = path.resolve(vault);\n const files = await fsAsync.readdir(vault, { recursive: true, withFileTypes: true });\n for (const f of files) {\n const fullPath = path.join(f.parentPath, f.name);\n const vaultPath = path.relative(vault, fullPath).split(path.sep).join(\"/\");\n if (!vaultPath.startsWith(configDir + \"/\")) {\n if (f.isDirectory()) {\n newVault.set(vaultPath, {type: 'folder'});\n } else {\n newVault.set(vaultPath, {type: 'file', sourceFile: fullPath});\n }\n }\n }\n } else {\n vault = _.mapKeys(vault, (v, k) => normalizePath(k));\n for (const [file, content] of Object.entries(vault)) {\n newVault.set(file, {type: \"file\", sourceContent: content});\n }\n const folders = new Set(Object.keys(vault).map(p => path.posix.dirname(p)).filter(p => p !== \".\"));\n for (const folder of folders) {\n newVault.set(folder, {type: \"folder\"});\n }\n }\n }\n\n // list all files in the current vault\n type CurrFileInfo = {type: \"file\"|\"folder\", hash?: string};\n const currVault = new Map(await this.browser.executeObsidian(({app}, configDir) => {\n async function hash(data: ArrayBuffer) {\n const hashBuffer = await window.crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n }\n\n async function listRecursive(path: string): Promise<[string, CurrFileInfo][]> {\n const result: [string, CurrFileInfo][] = [];\n const { folders, files } = await app.vault.adapter.list(path);\n for (const folder of folders) {\n if (!folder.startsWith(configDir + \"/\")) {\n result.push([folder, {type: \"folder\"}]);\n result.push(...await listRecursive(folder));\n }\n }\n for (const file of files) {\n if (!file.startsWith(configDir + \"/\")) {\n let fileHash = (app as AppInternal).metadataCache.getFileInfo(file)?.hash;\n if (!fileHash) { // hidden files etc. aren't in Obsidian's metadata cache\n fileHash = await hash(await app.vault.adapter.readBinary(file));\n }\n result.push([file, { type: \"file\", hash: fileHash }]);\n }\n }\n return result;\n }\n\n return listRecursive(\"/\");\n }, configDir));\n\n // delete any files that need to be deleted\n for (const file of [...currVault.keys()].sort().reverse()) {\n const currFileInfo = currVault.get(file)!\n if (!newVault.has(file) || newVault.get(file)!.type != currFileInfo.type) {\n await this.delete(file);\n }\n }\n\n // create files and folders\n for (const [file, newFileInfo] of _.sortBy([...newVault.entries()], 0)) {\n const currFileInfo = currVault.get(file);\n if (newFileInfo.type == \"file\") {\n let content = newFileInfo.sourceContent;\n if (!content) {\n content = (await fsAsync.readFile(newFileInfo.sourceFile!)).buffer as ArrayBuffer;\n }\n const hash = crypto.createHash(\"SHA256\")\n .update(typeof content == \"string\" ? content : new Uint8Array(content))\n .digest(\"hex\");\n if (!currFileInfo || currFileInfo.hash != hash) {\n await this.write(file, content);\n }\n } else if (newFileInfo.type == \"folder\" && !currFileInfo) {\n await this.mkdir(file);\n }\n }\n }\n}\n\n/**\n * Info on the platform we are running on or emulating, in similar format as\n * [obsidian.Platform](https://docs.obsidian.md/Reference/TypeScript+API/Platform)\n * @category Types\n */\nexport interface Platform {\n /**\n * The UI is in desktop mode.\n */\n isDesktop: boolean;\n /**\n * The UI is in mobile mode.\n */\n isMobile: boolean;\n /**\n * We're running the Electron-based desktop app.\n * Note, when running under `emulateMobile` this will still be true and isDesktop will be false.\n */\n isDesktopApp: boolean;\n /**\n * We're running the Capacitor-js mobile app.\n * Note, when running under `emulateMobile` this will still be false and isMobile will be true.\n */\n isMobileApp: boolean;\n /** \n * We're running the iOS app.\n * Note, wdio-obsidian-service doesn't support iOS yet, so this will always be false.\n */\n isIosApp: boolean;\n /**\n * We're running the Android app.\n */\n isAndroidApp: boolean;\n /**\n * We're in a mobile app that has very limited screen space.\n */\n isPhone: boolean;\n /**\n * We're in a mobile app that has sufficiently large screen space.\n */\n isTablet: boolean;\n /**\n * We're on a macOS device, or a device that pretends to be one (like iPhones and iPads).\n * Typically used to detect whether to use command-based hotkeys vs ctrl-based hotkeys.\n */\n isMacOS: boolean;\n /**\n * We're on a Windows device.\n */\n isWin: boolean;\n /**\n * We're on a Linux device.\n */\n isLinux: boolean;\n /**\n * We're running in Safari.\n * Note, wdio-obsidian-service doesn't support iOS yet, so this will always be false.\n */\n isSafari: boolean;\n};\n\n/**\n * Instance of {@link ObsidianPage} with helper methods for writing Obsidian tests.\n * @category Utilities\n */\nconst obsidianPage = new ObsidianPage()\nexport default obsidianPage;\nexport { ObsidianPage };\n","import type { ObsidianBrowserCommands } from \"./browserCommands.js\";\nimport type { PluginEntry, ThemeEntry, DownloadedPluginEntry, DownloadedThemeEntry } from \"obsidian-launcher\";\n\nexport const OBSIDIAN_CAPABILITY_KEY = \"wdio:obsidianOptions\";\n\n/**\n * Options passed to an \"wdio:obsidianOptions\" capability in wdio.conf.mts. E.g.\n * ```ts\n * // ...\n * capabilities: [{\n * browserName: \"obsidian\",\n * browserVersion: \"latest\",\n * 'wdio:obsidianOptions': {\n * installerVersion: 'earliest',\n * plugins: [\".\"],\n * },\n * }],\n * ```\n * \n * @category Options\n */\nexport interface ObsidianCapabilityOptions {\n /**\n * Version of Obsidian to download and run.\n * \n * Can be set to a specific version or one of:\n * - \"latest\": Run the latest non-beta Obsidian version\n * - \"latest-beta\": Run the latest beta Obsidian version (or latest if there is no current beta)\n * - To download Obsidian beta versions you'll need to have an Obsidian Insiders account and either set the \n * `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` environment variables (`.env` file is supported) or pre-download\n * the Obsidian beta version with `npx obsidian-launcher download app -v latest-beta`\n * - \"earliest\": Run the `minAppVersion` set in your `manifest.json`\n * \n * Defaults to \"latest\".\n * \n * You can also use the wdio capability `browserVersion` field to set the Obsidian version.\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n */\n appVersion?: string\n\n /**\n * Version of the Obsidian installer to download and run.\n * \n * Obsidian is Desktop distributed in two parts, the app which contains the JS, and the installer which is the\n * binary with Electron. Obsidian's auto update only updates the app, so users on the same Obsidian version can be\n * running different Electron versions. You can use this to test your plugin against different installer/Electron\n * versions.\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n *\n * Can be set to a specific version string or one of:\n * - \"latest\": Run the latest Obsidian installer compatible with `appVersion`.\n * - \"earliest\": Run the oldest Obsidian installer compatible with `appVersion`.\n * \n * Defaults to \"earliest\".\n */\n installerVersion?: string,\n\n /**\n * List of plugins to install.\n * \n * Each entry is a path to the local plugin to install, e.g. [\".\"] or [\"dist\"] depending on your build setup. Paths\n * are relative to your `wdio.conf.mts`. You can also pass objects. If you pass an object it should contain one of\n * `path` (to install a local plugin), `repo` (to install a plugin from GitHub), or `id` (to install a community\n * plugin). You can set `enabled: false` to install the plugin but start it disabled. You can enable the plugin\n * later using {@link ObsidianBrowserCommands.reloadObsidian|browser.reloadObsidian} or\n * {@link ObsidianPage.enablePlugin}.\n */\n plugins?: PluginEntry[],\n\n /**\n * List of themes to install.\n * \n * Each entry is a path to the local theme to install. Paths are relative to your `wdio.conf.mts`. You can also pass\n * an object. If you pass an object it should contain one of `path` (to install a local theme), `repo` (to install a\n * theme from GitHub), or `name` (to install a community theme). You can set `enabled: false` to install the theme,\n * but start it disabled. You can only have one enabled theme, so if you pass multiple you'll have to disable all\n * but one.\n */\n themes?: ThemeEntry[],\n\n /**\n * The path to the vault to open.\n * \n * The vault will be copied, so any changes made in your tests won't affect the original. If omitted, no vault will\n * be opened and you'll need to call {@link ObsidianBrowserCommands.reloadObsidian|browser.reloadObsidian} to open a\n * vault during your tests. Path is relative to your `wdio.conf.mts`.\n */\n vault?: string,\n\n /**\n * Set to true to emulate mobile on the Electron desktop app. This uses Obsidian `app.emulateMobile()` to switch\n * Obsidian to the mobile UI, and you can use Chrome's mobileEmulation to set the screen size. You can compare\n * tablet vs phone UIs by setting the screen size or emulated device. Obsidian tablet UI triggers at\n * width/height >= 600.\n *\n * Note that Obsidian Mobile runs on Capacitor instead of Electron so there are various platform differences that\n * can't be emulated. But it's good enough for most cases as long as you aren't interacting directly with the\n * operating system or Electron APIs. You can use an Android Virtual Device instead if you want a more accurate\n * (but slower) mobile test.\n *\n * See [Mobile Emulation](../README.md#mobile-emulation) for more info.\n * See [Android](../README.md#android) if you want to test the real mobile app on an Android Virtual Device instead.\n */\n emulateMobile?: boolean,\n\n /**\n * Path to the Obsidian binary to use. If omitted it will be downloaded automatically.\n */\n binaryPath?: string,\n\n /**\n * Path to the app asar to load into obsidian. If omitted it will be downloaded automatically.\n */\n appPath?: string,\n}\n\n\n/** Internal type, capability options after being normalized by onPrepare */\nexport interface NormalizedObsidianCapabilityOptions {\n appVersion: string\n installerVersion: string,\n plugins: DownloadedPluginEntry[],\n themes: DownloadedThemeEntry[],\n vault?: string,\n vaultCopy?: string,\n /** Path of the vault on the appium device */\n uploadedVault?: string,\n emulateMobile: boolean,\n binaryPath?: string,\n appPath?: string,\n}\n\n\n/**\n * Options passed to wdio-obsidian-service service in wdio.conf.mts. E.g.\n * ```js\n * // ...\n * services: [[\"obsidian\", {versionsUrl: \"file:///path/to/obsidian-versions.json\"}]]\n * ```\n * You'll usually want to leave these options as the default, they are mostly useful for wdio-obsidian-service's\n * internal tests.\n * \n * @category Options\n */\nexport interface ObsidianServiceOptions {\n /**\n * Override the `obsidian-versions.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/jesse-r-s-hines/wdio-obsidian-service/HEAD/obsidian-versions.json\n * which is auto-updated to contain information on available Obsidian versions.\n */\n versionsUrl?: string,\n /**\n * Override the `community-plugins.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-plugins.json\n */\n communityPluginsUrl?: string,\n /**\n * Override the `community-css-themes.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-css-themes.json\n */\n communityThemesUrl?: string,\n}\n\ndeclare global {\n namespace WebdriverIO {\n interface Capabilities {\n [OBSIDIAN_CAPABILITY_KEY]?: ObsidianCapabilityOptions,\n }\n\n interface Browser extends ObsidianBrowserCommands {}\n }\n}\n","// src/index.ts\nvar globals = globalThis._wdioGlobals = globalThis._wdioGlobals || /* @__PURE__ */ new Map();\nvar GLOBALS_ERROR_MESSAGE = `No browser instance registered. Don't import @wdio/globals outside of the WDIO testrunner context. Or you have two two different \"@wdio/globals\" packages installed.`;\nfunction proxyHandler(key) {\n return {\n get: (self, prop) => {\n if (!globals.has(key)) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n const receiver = globals.get(key);\n const field = receiver[prop];\n return typeof field === \"function\" ? field.bind(receiver) : field;\n }\n };\n}\nvar browser = new Proxy(\n class Browser {\n },\n proxyHandler(\"browser\")\n);\nvar driver = new Proxy(\n class Browser2 {\n },\n proxyHandler(\"driver\")\n);\nvar multiremotebrowser = new Proxy(\n class Browser3 {\n },\n proxyHandler(\"multiremotebrowser\")\n);\nvar $ = (...args) => {\n if (!globals.has(\"$\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"$\")(...args);\n};\nvar $$ = (...args) => {\n if (!globals.has(\"$$\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"$$\")(...args);\n};\nvar expect = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\")(...args);\n};\nvar ASYNC_MATCHERS = [\n \"any\",\n \"anything\",\n \"arrayContaining\",\n \"objectContaining\",\n \"stringContaining\",\n \"stringMatching\"\n];\nfor (const matcher of ASYNC_MATCHERS) {\n expect[matcher] = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\")[matcher](...args);\n };\n}\nexpect.not = ASYNC_MATCHERS.reduce((acc, matcher) => {\n acc[matcher] = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\").not[matcher](...args);\n };\n return acc;\n}, {});\nexpect.extend = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n const expect2 = globals.get(\"expect\");\n return expect2.extend(...args);\n};\nfunction _setGlobal(key, value, setGlobal = true) {\n globals.set(key, value);\n if (setGlobal) {\n globalThis[key] = value;\n }\n}\nexport {\n $,\n $$,\n _setGlobal,\n browser,\n driver,\n expect,\n multiremotebrowser\n};\n","import * as wdioGlobals from \"@wdio/globals\"\n\n/**\n * Base page object for use in the wdio [page object pattern](https://webdriver.io/docs/pageobjects).\n * \n * You can pass the browser to the page object, which allows using the object even in wdio standalone mode.\n */\nexport class BasePage {\n private _browser: WebdriverIO.Browser|undefined\n constructor(browser?: WebdriverIO.Browser) {\n this._browser = browser;\n }\n\n /**\n * Returns the browser instance.\n * @hidden\n */\n protected get browser(): WebdriverIO.Browser {\n return this._browser ?? wdioGlobals.browser;\n }\n}\n","import path from \"path\";\nimport crypto from \"crypto\";\nimport fsAsync from \"fs/promises\";\nimport * as tar from \"tar\";\nimport _ from \"lodash\";\n\n/** Quote string for use in shell scripts */\nexport function quote(input: string) {\n return `'${input.replace(/'/g, \"'\\\\''\")}'`;\n}\n\nexport async function fileExists(path: string) {\n try {\n await fsAsync.access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Gets the appium options.\n * Handles combining `\"appium:foo\"` and `\"appium:options\": {\"foo\": ...}` style options.\n */\nexport function getAppiumOptions(\n cap: WebdriverIO.Capabilities,\n): Exclude<WebdriverIO.Capabilities['appium:options'], undefined> {\n let result = _.pickBy(cap, (v, k) => k.startsWith(\"appium:\") && k != 'appium:options');\n result = _.mapKeys(result, (v, k) => k.slice(7))\n return {...result, ...cap['appium:options']};\n}\n\n/** Returns true if this capability is for Appium */\nexport function isAppium(cap: WebdriverIO.Capabilities) {\n return getAppiumOptions(cap).automationName?.toLocaleLowerCase() === 'uiautomator2';\n}\n\n/**\n * Upload multiple files at once. Uploads files as a tar for much better performance and to avoid issues with special\n * characters in names. (see https://github.com/appium/appium-android-driver/issues/1004)\n * \n * @param opts.src Directory to copy from (-C in tar)\n * @param opts.dest Directory to copy to\n * @param opts.files Files under src to copy. Defaults to the whole dir.\n * @param opts.chunkSize Size to split the tar into while uploading (to avoid excessive RAM usage)\n */\nexport async function appiumUploadFiles(browser: WebdriverIO.Browser, opts: {\n src: string, dest: string, files?: string[], chunkSize?: number,\n}) {\n let {\n src, dest,\n files = [src],\n chunkSize = 2 * 1024 * 1024,\n } = opts;\n src = path.resolve(src);\n dest = path.posix.normalize(dest).replace(/\\/$/, '');\n files = files.map(f => path.relative(src, f) || \".\");\n if (files.length == 0) return; // nothing to do\n\n // We'll tar the files up and then extract the tar on Android side. This is a lot faster than doing many small\n // pushFiles. Since pushFile requires sending the whole file contents as a base64 string, we'll split up the tar\n // into chunks to keep the size manageable for large vaults.\n\n const tmpDir = \"/data/local/tmp\"\n const slug = crypto.randomBytes(10).toString(\"base64url\").replace(/[-_]/g, '0');\n const stream = tar.create({C: src, gzip: { level: 2 }}, files);\n\n // buffer/chunk size of the tar stream varies. Without compression it appears to be one chunk per file, splitting\n // large files at maxReadSize, plus some chunks for the header. However compression adds another layer that appears\n // to limit the output chunk size to about 16K.\n let buffers: Buffer[] = [];\n let bufferSize = 0;\n let i = 0;\n for await (const chunk of stream) {\n if (bufferSize + chunk.length > chunkSize && bufferSize > 0) {\n const data = Buffer.concat(buffers).toString('base64');\n await browser.pushFile(`${tmpDir}/${slug}-${String(i).padStart(6, '0')}.tar`, data);\n i++;\n buffers = [];\n bufferSize = 0;\n }\n buffers.push(chunk);\n bufferSize += chunk.length;\n }\n const data = Buffer.concat(buffers).toString('base64');\n await browser.pushFile(`${tmpDir}/${slug}-${String(i).padStart(6, '0')}.tar`, data);\n\n // extract the tar. `mobile: shell` does NOT escape arguments despite taking an argument array.\n await browser.execute(\"mobile: shell\", {command: \"sh\", args: [\"-c\", quote(`\n mkdir -p ${quote(dest)};\n cat ${tmpDir}/${slug}-*.tar | tar -xz -C ${quote(dest)};\n rm ${tmpDir}/${slug}-*.tar;\n `)]});\n}\n\nexport async function appiumDownloadFile(browser: WebdriverIO.Browser, src: string, dest: string) {\n src = path.posix.normalize(src);\n dest = path.resolve(dest);\n const content = Buffer.from(await browser.pullFile(src), \"base64\");\n await fsAsync.writeFile(dest, content);\n}\n\n/** Lists all files under a folder. Returns full paths. */\nexport async function appiumReaddir(browser: WebdriverIO.Browser, dir: string) {\n // list all files, one per line, no escaping\n const stdout: string = await browser.execute(\"mobile: shell\", {command: \"ls\", args: [\"-NA1\", dir]});\n return stdout.split(\"\\n\").filter(f => f).map(f => path.join(dir, f)).sort();\n}\n\n/**\n * Reload/replace the page and then wait for the current page to unloaded. Does NOT wait for the new page to be ready.\n * Example:\n * ```ts\n * await navigateAndWait(browser, () => location.reload());\n * ```\n */\nexport async function navigateAndWait(browser: WebdriverIO.Browser, func: () => void) {\n await browser.execute(() => { (window as any)._waitingForNavigation = true });\n await browser.execute(func);\n await browser.waitUntil(() => browser.execute(() => !('_waitingForNavigation' in window)));\n}\n\n/** SHA256 hash */\nexport async function hash(content: string|ArrayBufferLike) {\n return crypto.createHash(\"SHA256\")\n .update(typeof content == \"string\" ? content : new Uint8Array(content))\n .digest(\"hex\");\n}\n\n/** Replicate obsidian.normalizePath */\nexport function normalizePath(p: string) {\n p = p.replace(/([\\\\/])+/g, \"/\").replace(/(^\\/+|\\/+$)/g, \"\");\n if (p == \"\") {\n p = \"/\"\n }\n p = p.replace(/\\u00A0|\\u202F/g, \" \"); // replace special space characters\n p = p.normalize(\"NFC\");\n return p;\n}\n\n/** Returns true if a vault file path is hidden (either it or one of it's parent directories starts with \".\") */\nexport function isHidden(file: string) {\n return file.split(\"/\").some(p => p.startsWith(\".\"))\n}\n\n/** Returns true if this is a simple text file */\nexport function isText(file: string) {\n return [\".md\", \".json\", \".txt\", \".js\"].includes(path.extname(file).toLocaleLowerCase());\n}\n","import type * as obsidian from \"obsidian\"\nimport { ObsidianPage } from \"./pageobjects/obsidianPage.js\"\nimport { NormalizedObsidianCapabilityOptions, OBSIDIAN_CAPABILITY_KEY } from \"./types.js\";\nimport { AppInternal } from \"./obsidianTypes.js\";\n\nexport const browserCommands = {\n /**\n * Wrapper around browser.execute that passes the Obsidian API to the function. The first argument to the function\n * is an object containing keys:\n * - app: Obsidian app instance\n * - obsidian: Full Obsidian API\n * - plugins: Object of all installed plugins, mapped by plugin id converted to camelCase.\n * - require: The customized require function Obsidian makes available to plugins. This is also available globally,\n * so you can just use `require` directly instead of from {@link ExecuteObsidianArg} if you prefer.\n * \n * The same caveats as `browser.execute` apply. The function will be stringified and then run inside Obsidian, so\n * you can't capture any local variables. Instead, pass inputs as extra arguments to the function.\n * \n * See also: https://webdriver.io/docs/api/browser/execute\n * \n * Example usage:\n * ```ts\n * const stats = await browser.executeObsidian(({app}, path) => {\n * return app.vault.getMarkdownFiles().find(f => f.path == path)!.stat;\n * }, \"Welcome.md\");\n * ```\n * \n * Make sure not to capture Obsidian types, instead use the provided obsidian module object. E.g. \n * This *won't* work:\n * ```ts\n * import { FileView } from \"obsidian\"\n * browser.executeObsidian(({app}) => {\n * if (leaf.view instanceof FileView) {\n * ...\n * }\n * })\n * ```\n * do this instead:\n * ```ts\n * browser.executeObsidian(({app, obsidian}) => {\n * if (leaf.view instanceof obsidian.FileView) {\n * ...\n * }\n * })\n * ```\n */\n async executeObsidian<Return, Params extends unknown[]>(\n this: WebdriverIO.Browser,\n func: (obs: ExecuteObsidianArg, ...params: Params) => Return,\n ...params: Params\n ): Promise<Return> {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n if (obsidianOptions.vault === undefined) {\n throw Error(\"No vault open, set vault in wdio.conf.mts or use reloadObsidian to open a vault dynamically.\")\n }\n // Call the function. Just stringify the function (browser.execute stringifies it anyways). Add a workaround for\n // node fs errors returning useless error messages. If the thrown error has a \"code\" field that is a string, the\n // chromedriver throws an error like this with no further info:\n // \"unknown error: call function result missing int 'status'\"\n // I think this is a bug in ChromeDevtoolsProtocol or chromedriver. See\n // https://github.com/electron-userland/spectron/issues/1057\n const result = await this.execute<Return, Params>(`\n const require = window.wdioObsidianService().require;\n try {\n return await (\n ${func.toString()}\n ).call(null, window.wdioObsidianService(), ...arguments);\n } catch (e) {\n if (\"code\" in e && typeof e.code != \"number\") {\n delete e.code;\n }\n throw e;\n }\n `, ...params);\n // TODO Should maybe add in the TransformReturn and TransformElement bit that wdio has recently added to the\n // execute types, though it causes weird affects if `func` returns type any.\n return result as Return;\n },\n\n /**\n * Executes an Obsidian command by id.\n * @param id Id of the command to run.\n */\n async executeObsidianCommand(this: WebdriverIO.Browser, id: string) {\n const result = await this.executeObsidian(\n ({app}, id) => (app as AppInternal).commands.executeCommandById(id),\n id,\n );\n if (!result) {\n throw Error(`Obsidian command ${id} not found or failed.`);\n }\n },\n\n /**\n * Returns the Obsidian app version this test is running under.\n */\n getObsidianVersion(this: WebdriverIO.Browser): string {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n return obsidianOptions.appVersion;\n },\n \n /**\n * Returns the Obsidian installer version this test is running under.\n */\n getObsidianInstallerVersion(this: WebdriverIO.Browser): string {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n return obsidianOptions.installerVersion;\n },\n\n /**\n * Returns the ObsidianPage object with convenience helper functions.\n * You can also just import the page object directly with\n * ```ts\n * import { obsidianPage } from \"wdio-obsidian-service\"\n * ```\n */\n getObsidianPage(this: WebdriverIO.Browser): ObsidianPage {\n return new ObsidianPage(this);\n },\n}\n\n/**\n * Extra commands added to the WDIO Browser instance.\n * \n * See also: https://webdriver.io/docs/api/browser\n * @interface\n * @category Utilities\n */\nexport type ObsidianBrowserCommands = typeof browserCommands & {\n /**\n * Relaunch obsidian. Can be used to switch to a new vault, change the plugin list, or just to reboot Obsidian.\n * \n * As this does a full reboot of Obsidian, this is rather slow. In many cases you can use\n * {@link ObsidianPage.resetVault} instead, which modifies vault files in place without rebooting Obsidian. If all\n * your tests use the same vault, you can also just set the vault in the `wdio.conf.mts` capabilities section.\n * \n * @param params.vault Path to the vault to open. The vault will be copied, so any changes made in your tests won't\n * be persited to the original. If omitted, it will reboot Obsidian with the current vault without creating a\n * new copy of the vault.\n * @param params.plugins List of plugin ids to enable. If omitted it will keep current plugin list. Note, all the\n * plugins must be defined in your wdio.conf.mts capabilities. You can also use the enablePlugin and \n * disablePlugin commands to change plugins without relaunching Obsidian.\n * @param params.theme Name of the theme to enable. If omitted it will keep the current theme. Pass \"default\" to\n * switch back to the default theme. Like with plugins, the theme must be defined in wdio.conf.mts.\n */\n reloadObsidian(params?: {\n vault?: string,\n plugins?: string[], theme?: string,\n }): Promise<void>;\n // This command is implemented in the service hooks.\n};\n\n/**\n * Argument passed to the {@link ObsidianBrowserCommands.executeObsidian | executeObsidian} browser command.\n * @category Types\n */\nexport interface ExecuteObsidianArg {\n /**\n * There is a global \"app\" instance, but that may be removed in the future so you can use this to access it from\n * tests. See https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines#Avoid+using+global+app+instance\n */\n app: obsidian.App,\n\n /**\n * The full obsidian API. See https://github.com/obsidianmd/obsidian-api/blob/master/obsidian.d.ts\n */\n obsidian: typeof obsidian,\n\n /**\n * Object containing all installed plugins mapped by their id. Plugin ids are converted to camelCase for ease of\n * destructuring.\n * \n * If you want to add typings for your plugin(s) you can use something like this in a `.d.ts`:\n * ```ts\n * import type MyPlugin from \"../src/main.js\"\n * declare module \"wdio-obsidian-service\" {\n * interface InstalledPlugins {\n * myPlugin: MyPlugin,\n * }\n * }\n * ```\n */\n plugins: InstalledPlugins,\n\n /**\n * The customized require function Obsidian makes available to plugins. This is also available globally, so you can\n * just use `require` directly instead of from {@link ExecuteObsidianArg} if you prefer.\n */\n require: NodeJS.Require,\n}\n\n/**\n * Installed plugins, mapped by their id converted to camelCase\n * @category Types\n */\nexport interface InstalledPlugins extends Record<string, obsidian.Plugin> {\n}\n","import { remote } from 'webdriverio'\nimport type { Capabilities, Options } from '@wdio/types'\nimport { ObsidianLauncherService, ObsidianWorkerService } from \"./service.js\"\nimport { ObsidianServiceOptions } from \"./types.js\"\n\n/**\n * Starts an Obsidian instance for WDIO standalone mode.\n * \n * For testing, you'll usually want to use the WDIO testrunner with Mocha, wdio.conf.mts, etc. to launch WDIO. However\n * if you want to use WDIO for some kind of scripting scenario, you can use this function to launch a WDIO standalone\n * session connected to Obsidian.\n * \n * See also: https://webdriver.io/docs/setuptypes/#standalone-mode\n * \n * Example:\n * ```ts\n * const browser = await startWdioSession({\n * capabilities: {\n * browserName: \"obsidian\",\n * browserVersion: \"latest\",\n * 'wdio:obsidianOptions': {\n * installerVersion: \"latest\",\n * vault: \"./test/vaults/basic\",\n * },\n * },\n * });\n * await browser.executeObsidian(({app}) => {\n * // extract some file metadata, edit the vault, etc...\n * });\n * await browser.deleteSession();\n * ```\n * \n * Note that in standalone mode, the global `obsidianPage` instance won't work, you have to use\n * {@link ObsidianBrowserCommands.getObsidianPage | getObsidianPage} to get the page object, e.g.:\n * ```js\n * const obsidianPage = browser.getObsidianPage()\n * ```\n * instead of the \n * @category WDIO Helpers\n */\nexport async function startWdioSession(\n params: Capabilities.WebdriverIOConfig,\n serviceOptions?: ObsidianServiceOptions,\n): Promise<WebdriverIO.Browser> {\n serviceOptions = serviceOptions ?? {};\n const capabilities = params.capabilities as WebdriverIO.Capabilities;\n const testRunnerOptions: Options.Testrunner = {\n cacheDir: params.cacheDir,\n };\n const launcherService = new ObsidianLauncherService(\n serviceOptions,\n [capabilities] as WebdriverIO.Capabilities,\n testRunnerOptions,\n );\n const workerService = new ObsidianWorkerService(serviceOptions, capabilities, testRunnerOptions);\n\n await launcherService.onPrepare(testRunnerOptions, [capabilities]);\n await workerService.beforeSession(testRunnerOptions, capabilities);\n\n const browser = await remote(params);\n\n await workerService.before(capabilities, [], browser);\n\n return browser;\n}\n"],"mappings":"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","names":["ObsidianLauncher","fsAsync","path","fileURLToPath","path","fsAsync","crypto","_","browser","path","browser","data","browser","_","pluginId","themeName","path","workspacesPath","layout","file","strContent","binContent","parent","configDir","id","semver","_","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.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "A WebdriverIO service for end-to-end testing of Obsidian plugins",
5
5
  "keywords": [
6
6
  "obsidian",
@@ -56,11 +56,11 @@
56
56
  "tsup": "~8.5.1",
57
57
  "tsx": "~4.21.0",
58
58
  "typescript": "~5.9.3",
59
- "wdio-obsidian-reporter": "2.2.1"
59
+ "wdio-obsidian-reporter": "2.3.0"
60
60
  },
61
61
  "dependencies": {
62
62
  "lodash": "~4.17.21",
63
- "obsidian-launcher": "2.2.1",
63
+ "obsidian-launcher": "2.3.0",
64
64
  "semver": "~7.7.3",
65
65
  "tar": "~7.5.3"
66
66
  },
@@ -71,7 +71,7 @@
71
71
  "@wdio/mocha-framework": "^9.18.0",
72
72
  "appium": ">=2.19.0",
73
73
  "appium-uiautomator2-driver": ">=4.2.7",
74
- "obsidian": "^1.8.7",
74
+ "obsidian": ">=1.1.1",
75
75
  "webdriverio": "^9.18.1"
76
76
  },
77
77
  "peerDependenciesMeta": {