wdio-obsidian-service 0.3.0 → 0.4.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/dist/index.d.ts CHANGED
@@ -1,32 +1,85 @@
1
1
  import { Services, Options, Capabilities } from '@wdio/types';
2
2
  import * as obsidian from 'obsidian';
3
3
  import { PluginEntry, ThemeEntry } from 'obsidian-launcher';
4
- export { CommunityPluginEntry, CommunityThemeEntry, GitHubPluginEntry, GitHubThemeEntry, LocalPluginEntry, LocalThemeEntry, PluginEntry, ThemeEntry } from 'obsidian-launcher';
4
+ export { DownloadedPluginEntry, DownloadedThemeEntry, PluginEntry, ThemeEntry } from 'obsidian-launcher';
5
5
 
6
6
  /**
7
7
  * Class with various helper methods for writing Obsidian tests using the
8
8
  * [page object pattern](https://webdriver.io/docs/pageobjects).
9
+ *
10
+ * You can get an instance of this class either by running
11
+ * ```ts
12
+ * const obsidianPage = await browser.getObsidianPage();
13
+ * ```
14
+ * or just importing it directly with
15
+ * ```ts
16
+ * import { obsidianPage } from "wdio-obsidian-service";
17
+ * ```
9
18
  */
10
19
  declare class ObsidianPage {
11
- /** Enables a plugin */
20
+ /**
21
+ * Returns the path to the vault opened in Obsidian.
22
+ *
23
+ * wdio-obsidian-service copies your vault before running tests, so this is the path to the temporary copy.
24
+ */
25
+ getVaultPath(): Promise<string | undefined>;
26
+ /**
27
+ * Enables a plugin by ID
28
+ */
12
29
  enablePlugin(pluginId: string): Promise<void>;
13
- /** Disables a plugin */
30
+ /**
31
+ * Disables a plugin by ID
32
+ */
14
33
  disablePlugin(pluginId: string): Promise<void>;
15
- /** Sets the theme. Pass "default" to reset to the Obsidian theme. */
34
+ /**
35
+ * Sets the theme. Pass "default" to reset to the Obsidian theme.
36
+ */
16
37
  setTheme(themeName: string): Promise<void>;
17
38
  /**
18
39
  * Opens a file in a new tab.
19
40
  */
20
41
  openFile(path: string): Promise<void>;
21
42
  /**
22
- * Loads a saved workspace from `workspaces.json` by name. You can use the core "Workspaces" plugin to create the
23
- * layouts.
43
+ * Loads a saved workspace layout from `.obsidian/workspaces.json` by name. Use the core "Workspaces"
44
+ * plugin to create the layouts. You can also pass the layout object directly.
24
45
  */
25
- loadWorkspaceLayout(layoutName: string): Promise<void>;
46
+ loadWorkspaceLayout(layout: any): Promise<void>;
47
+ /**
48
+ * Resets the vault files to the original state by deleting/creating/modifying vault files in place without
49
+ * reloading Obsidian.
50
+ *
51
+ * This will only reset regular vault files, it won't touch anything under `.obsidian`, and it won't reset any
52
+ * config and app state you've set in Obsidian. But if all you need is to reset the vault files, this can be used as
53
+ * a faster alternative to reloadObsidian.
54
+ *
55
+ * If no vault is passed, it resets the vault back to the oringal vault opened by the tests. You can also pass a
56
+ * path to an different vault, and it will sync the current vault to match that one (similar to "rsync"). Or,
57
+ * instead of passing a vault path you can pass an object mapping vault file paths to file content. E.g.
58
+ * ```ts
59
+ * obsidianPage.resetVault({
60
+ * 'path/in/vault.md': "Hello World",
61
+ * })
62
+ * ```
63
+ *
64
+ * You can also pass multiple vaults and the files will be merged. This can be useful if you want to add a few small
65
+ * modifications to the base vault. e.g:
66
+ * ```ts
67
+ * obsidianPage.resetVault('./path/to/vault', {
68
+ * "books/leviathan-wakes.md": "...",
69
+ * })
70
+ * ```
71
+ */
72
+ resetVaultFiles(...vaults: (string | Record<string, string>)[]): Promise<void>;
26
73
  }
74
+ /**
75
+ * Instance of ObsidianPage with helper methods for writing Obsidian tests
76
+ */
27
77
  declare const obsidianPage: ObsidianPage;
28
78
 
29
- type ExecuteObsidianArg = {
79
+ /**
80
+ * Argument passed to the `executeObsidian` browser command.
81
+ */
82
+ interface ExecuteObsidianArg {
30
83
  /**
31
84
  * There is a global "app" instance, but that may be removed in the future so you can use this to access it from
32
85
  * tests. See https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines#Avoid+using+global+app+instance
@@ -36,23 +89,34 @@ type ExecuteObsidianArg = {
36
89
  * The full obsidian API. See https://github.com/obsidianmd/obsidian-api/blob/master/obsidian.d.ts
37
90
  */
38
91
  obsidian: typeof obsidian;
39
- };
92
+ /**
93
+ * Object containing all installed plugins mapped by their id. Plugin ids are converted to converted to camelCase
94
+ * for ease of destructuring.
95
+ */
96
+ plugins: Record<string, obsidian.Plugin>;
97
+ }
40
98
  declare const browserCommands: {
41
- /** Returns the Obsidian version this test is running under. */
99
+ /**
100
+ * Returns the Obsidian app version this test is running under.
101
+ */
42
102
  readonly getObsidianVersion: (this: WebdriverIO.Browser) => Promise<string>;
43
- /** Returns the Obsidian installer version this test is running under. */
103
+ /**
104
+ * Returns the Obsidian installer version this test is running under.
105
+ */
44
106
  readonly getObsidianInstallerVersion: (this: WebdriverIO.Browser) => Promise<string>;
45
107
  /**
46
108
  * Wrapper around browser.execute that passes the Obsidian API to the function. The first argument to the function
47
109
  * is an object containing keys:
48
110
  * - app: Obsidian app instance
49
111
  * - obsidian: Full Obsidian API
112
+ * - plugins: Object of all installed plugins, mapped by plugin id converted to camelCase.
113
+ *
50
114
  * See also: https://webdriver.io/docs/api/browser/execute
51
115
  *
52
116
  * Example usage
53
117
  * ```ts
54
118
  * const file = browser.executeObsidian(({app, obsidian}, path) => {
55
- * return app.vault.getMarkdownFiles().find(f => f.path == path)
119
+ * return app.vault.getMarkdownFiles().find(f => f.path == path);
56
120
  * })
57
121
  * ```
58
122
  *
@@ -83,18 +147,48 @@ declare const browserCommands: {
83
147
  * @param id Id of the command to run.
84
148
  */
85
149
  readonly executeObsidianCommand: (this: WebdriverIO.Browser, id: string) => Promise<void>;
86
- /** Returns the path to the vault opened in Obsidian */
87
- readonly getVaultPath: (this: WebdriverIO.Browser) => Promise<string | undefined>;
88
150
  /**
89
151
  * Returns the Workspace page object with convenience helper functions.
90
- * You can also import the page object directly with
152
+ * You can also just import the page object directly with
91
153
  * ```ts
92
154
  * import { obsidianPage } from "wdio-obsidian-service"
93
155
  * ```
94
156
  */
95
157
  readonly getObsidianPage: (this: WebdriverIO.Browser) => Promise<ObsidianPage>;
96
158
  };
97
- type ObsidianBrowserCommands = typeof browserCommands;
159
+ /** Define this type separately so we can @inline it in typedoc */
160
+ type PlainObsidianBrowserCommands = typeof browserCommands;
161
+ /**
162
+ * Extra commands added to the WDIO Browser instance.
163
+ *
164
+ * See also: https://webdriver.io/docs/api/browser#custom-commands
165
+ * @interface
166
+ */
167
+ type ObsidianBrowserCommands = PlainObsidianBrowserCommands & {
168
+ /**
169
+ * Relaunch obsidian. Can be used to switch to a new vault, change the plugin list, or just to reboot
170
+ * Obsidian.
171
+ *
172
+ * As this does a full reboot of Obsidian, avoid calling this too often so you don't slow your tests down.
173
+ * You can also set the vault in the `wdio.conf.ts` capabilities section which may be useful if all your
174
+ * tests use the same vault.
175
+ *
176
+ * @param params.vault Path to the vault to open. The vault will be copied, so any changes made in your tests won't
177
+ * be persited to the original. If omitted, it will reboot Obsidian with the current vault, without
178
+ * creating a new copy of the vault.
179
+ * @param params.plugins List of plugin ids to enable. If omitted it will keep current plugin list. Note, all the
180
+ * plugins must be defined in your wdio.conf.ts capabilities. You can also use the enablePlugin and
181
+ * disablePlugin commands to change plugins without relaunching Obsidian.
182
+ * @param params.theme Name of the theme to enable. If omitted it will keep the current theme. Pass "default" to
183
+ * switch back to the default theme. Like with plugins, the theme must be defined in wdio.conf.ts.
184
+ * @returns Returns the new sessionId (same as browser.reloadSession()).
185
+ */
186
+ reloadObsidian(params?: {
187
+ vault?: string;
188
+ plugins?: string[];
189
+ theme?: string;
190
+ }): Promise<string>;
191
+ };
98
192
 
99
193
  declare const OBSIDIAN_CAPABILITY_KEY: "wdio:obsidianOptions";
100
194
  interface ObsidianServiceOptions {
@@ -116,28 +210,36 @@ interface ObsidianServiceOptions {
116
210
  }
117
211
  interface ObsidianCapabilityOptions {
118
212
  /**
119
- * Version of Obsidian to run.
213
+ * Version of Obsidian to download and run.
120
214
  *
121
- * Can be set to "latest", "latest-beta", or a specific version. Defaults to "latest". To download beta versions
122
- * you'll need to be an Obsidian insider and set the OBSIDIAN_USERNAME and OBSIDIAN_PASSWORD environment
123
- * variables.
215
+ * Can be set to a specific version or one of:
216
+ * - "latest": Run the current latest non-beta Obsidian version
217
+ * - "latest-beta": Run the current latest beta Obsidian version (or latest is there is no current beta)
218
+ * - "earliest": Run the `minAppVersion` set in set in your `manifest.json`
219
+ *
220
+ * Defaults to "latest".
221
+ *
222
+ * To download beta versions you'll need to have an Obsidian account with Catalyst and set the `OBSIDIAN_USERNAME`
223
+ * and `OBSIDIAN_PASSWORD` environment variables. 2FA needs to be disabled.
124
224
  *
125
225
  * You can also use the wdio capability `browserVersion` field to set the Obsidian version.
126
226
  */
127
227
  appVersion?: string;
128
228
  /**
129
- * Version of the Obsidian Installer to download.
229
+ * Version of the Obsidian installer to download and run.
130
230
  *
131
- * Note that Obsidian is distributed in two parts, the "installer", which is the executable containing Electron, and
231
+ * Note that Obsidian is distributed in two parts, the "installer" which is the executable containing Electron, and
132
232
  * the "app" which is a bundle of JavaScript containing the Obsidian code. Obsidian's self-update system only
133
233
  * updates the JavaScript bundle, and not the base installer/Electron version. This makes Obsidian's auto-update
134
234
  * fast as it only needs to download a few MiB of JS instead of all of Electron. But, it means different users with
135
- * the same Obsidian version may be running on different versions of Electron, which could cause subtle differences
136
- * in plugin behavior if you are using newer JavaScript features and the like in your plugin.
235
+ * the same Obsidian app version may be running on different versions of Electron, which can cause subtle
236
+ * differences in plugin behavior if you are using newer JavaScript features and the like in your plugin.
137
237
  *
138
- * If passed "latest", it will use the maximum installer version compatible with the selected Obsidian version. If
139
- * passed "earliest" it will use the oldest installer version compatible with the selected Obsidian version. The
140
- * default is "earliest".
238
+ * Can be set to a specific version string or one of:
239
+ * - "latest": Run the latest Obsidian installer.
240
+ * - "earliest": Run the oldest Obsidian installer compatible with the specified Obsidian app version.
241
+ *
242
+ * Defaults to "earliest".
141
243
  */
142
244
  installerVersion?: string;
143
245
  /**
@@ -166,9 +268,13 @@ interface ObsidianCapabilityOptions {
166
268
  * be opened and you'll need to call `browser.reloadObsidian` to open a vault during your tests.
167
269
  */
168
270
  vault?: string;
169
- /** Path to the Obsidian binary to use. If omitted it will download Obsidian automatically. */
271
+ /**
272
+ * Path to the Obsidian binary to use. If omitted it will be downloaded automatically.
273
+ */
170
274
  binaryPath?: string;
171
- /** Path to the app asar to load into obsidian. If omitted it will be downloaded automatically. */
275
+ /**
276
+ * Path to the app asar to load into obsidian. If omitted it will be downloaded automatically.
277
+ */
172
278
  appPath?: string;
173
279
  }
174
280
  declare global {
@@ -177,33 +283,22 @@ declare global {
177
283
  [OBSIDIAN_CAPABILITY_KEY]?: ObsidianCapabilityOptions;
178
284
  }
179
285
  interface Browser extends ObsidianBrowserCommands {
180
- /**
181
- * Relaunch obsidian. Can be used to switch to a new vault, change the plugin list, or just to reboot
182
- * Obsidian.
183
- *
184
- * As this does a full reboot of Obsidian, avoid calling this too often so you don't slow your tests down.
185
- * You can also set the vault in the `wdio.conf.ts` capabilities section which may be useful if all
186
- * your tests use the same vault.
187
- *
188
- * @param vault Path to the vault to open. The vault will be copied, so any changes made in your tests won't
189
- * be persited to the original. If omitted, it will reboot Obsidian with the current vault, without
190
- * creating a new copy of the vault.
191
- * @param plugins List of plugin ids to enable. If omitted it will keep current plugin list. Note, all the
192
- * plugins must be defined in your wdio.conf.ts capabilities. You can also use the enablePlugin and
193
- * disablePlugin commands to change plugins without relaunching Obsidian.
194
- * @param theme Name of the theme to enable. If omitted it will keep the current theme. Pass "default" to
195
- * switch back to the default theme. Like with plugins, the theme must be defined in wdio.conf.ts.
196
- * @returns Returns the new sessionId (same as browser.reloadSession()).
197
- */
198
- reloadObsidian(params?: {
199
- vault?: string;
200
- plugins?: string[];
201
- theme?: string;
202
- }): Promise<string>;
203
286
  }
204
287
  }
205
288
  }
206
289
 
290
+ /**
291
+ * Minimum Obsidian version that wdio-obsidian-service supports.
292
+ */
293
+ declare const minSupportedObsidianVersion: string;
294
+ /**
295
+ * wdio launcher service.
296
+ * Use in wdio.conf.ts like so:
297
+ * ```ts
298
+ * services: ['obsidian'],
299
+ * ```
300
+ * @hidden
301
+ */
207
302
  declare class ObsidianLauncherService implements Services.ServiceInstance {
208
303
  options: ObsidianServiceOptions;
209
304
  capabilities: WebdriverIO.Capabilities;
@@ -211,8 +306,19 @@ declare class ObsidianLauncherService implements Services.ServiceInstance {
211
306
  private obsidianLauncher;
212
307
  private readonly helperPluginPath;
213
308
  constructor(options: ObsidianServiceOptions, capabilities: WebdriverIO.Capabilities, config: Options.Testrunner);
309
+ /**
310
+ * Validates wdio:obsidianOptions and downloads Obsidian, plugins, and themes.
311
+ */
214
312
  onPrepare(config: Options.Testrunner, capabilities: Capabilities.TestrunnerCapabilities): Promise<void>;
215
313
  }
314
+ /**
315
+ * wdio worker service.
316
+ * Use in wdio.conf.ts like so:
317
+ * ```ts
318
+ * services: ['obsidian'],
319
+ * ```
320
+ * @hidden
321
+ */
216
322
  declare class ObsidianWorkerService implements Services.ServiceInstance {
217
323
  options: ObsidianServiceOptions;
218
324
  capabilities: WebdriverIO.Capabilities;
@@ -221,29 +327,53 @@ declare class ObsidianWorkerService implements Services.ServiceInstance {
221
327
  /** Directories to clean up after the tests */
222
328
  private tmpDirs;
223
329
  constructor(options: ObsidianServiceOptions, capabilities: WebdriverIO.Capabilities, config: Options.Testrunner);
330
+ /**
331
+ * Setup vault and config dir for a sandboxed Obsidian instance.
332
+ */
224
333
  private setupObsidian;
334
+ /**
335
+ * Wait for Obsidian to fully boot.
336
+ */
225
337
  private waitForReady;
338
+ /**
339
+ * Handles vault and sandboxed config directory setup.
340
+ */
226
341
  beforeSession(config: Options.Testrunner, capabilities: WebdriverIO.Capabilities): Promise<void>;
342
+ /**
343
+ * Returns a plugin list with only the selected plugin ids enabled.
344
+ */
227
345
  private selectPlugins;
346
+ /**
347
+ * Returns a theme list with only the selected theme enabled.
348
+ */
228
349
  private selectThemes;
350
+ /**
351
+ * Setup custom browser commands.
352
+ */
229
353
  before(capabilities: WebdriverIO.Capabilities, specs: never, browser: WebdriverIO.Browser): Promise<void>;
354
+ /**
355
+ * Cleanup
356
+ */
230
357
  afterSession(): Promise<void>;
231
358
  }
232
359
 
360
+ /** @hidden */
233
361
  declare const launcher: typeof ObsidianLauncherService;
234
362
 
235
363
  /**
236
364
  * Returns true if there is a current Obsidian beta and we have the credentials to download it, or its already in cache.
365
+ * @param cacheDir Obsidian cache dir, defaults to `.obsidian-cache`.
237
366
  */
238
367
  declare function obsidianBetaAvailable(cacheDir?: string): Promise<boolean>;
239
368
  /**
240
369
  * Resolves Obsidian app and installer version strings to absolute versions.
241
370
  * @param appVersion Obsidian version string or "latest", "latest-beta" or "earliest". "earliest" will use the
242
371
  * minAppVersion set in your manifest.json.
243
- * @param installerVersion Obsidian version string or "latest" or "earliest". "earliest" will use the minimum
372
+ * @param installerVersion Obsidian version string or "latest" or "earliest". "earliest" will use the oldest
244
373
  * installer version compatible with the appVersion.
374
+ * @param cacheDir Obsidian cache dir, defaults to `.obsidian-cache`.
245
375
  * @returns [appVersion, installerVersion] with any "latest" etc. resolved to specific versions.
246
376
  */
247
377
  declare function resolveObsidianVersions(appVersion: string, installerVersion: string, cacheDir?: string): Promise<[string, string]>;
248
378
 
249
- export { type ObsidianCapabilityOptions, type ObsidianServiceOptions, ObsidianWorkerService as default, launcher, obsidianBetaAvailable, obsidianPage, resolveObsidianVersions };
379
+ export { type ExecuteObsidianArg, type ObsidianBrowserCommands, type ObsidianCapabilityOptions, ObsidianPage, type ObsidianServiceOptions, ObsidianWorkerService as default, launcher, minSupportedObsidianVersion, obsidianBetaAvailable, obsidianPage, resolveObsidianVersions };
package/dist/index.js CHANGED
@@ -1,7 +1,15 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
1
8
  // src/index.ts
2
9
  import ObsidianLauncher2 from "obsidian-launcher";
3
10
 
4
11
  // src/service.ts
12
+ import fs from "fs";
5
13
  import fsAsync2 from "fs/promises";
6
14
  import path2 from "path";
7
15
  import { SevereServiceError } from "webdriverio";
@@ -15,22 +23,48 @@ var OBSIDIAN_CAPABILITY_KEY = "wdio:obsidianOptions";
15
23
  // src/pageobjects/obsidianPage.ts
16
24
  import * as path from "path";
17
25
  import * as fsAsync from "fs/promises";
26
+ import _ from "lodash";
18
27
  var ObsidianPage = class {
19
- /** Enables a plugin */
28
+ /**
29
+ * Returns the path to the vault opened in Obsidian.
30
+ *
31
+ * wdio-obsidian-service copies your vault before running tests, so this is the path to the temporary copy.
32
+ */
33
+ async getVaultPath() {
34
+ if (browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].vault == void 0) {
35
+ return void 0;
36
+ } else {
37
+ return await browser.executeObsidian(({ app, obsidian }) => {
38
+ if (app.vault.adapter instanceof obsidian.FileSystemAdapter) {
39
+ return app.vault.adapter.getBasePath();
40
+ } else {
41
+ throw new Error(`Unrecognized DataAdapater type`);
42
+ }
43
+ ;
44
+ });
45
+ }
46
+ }
47
+ /**
48
+ * Enables a plugin by ID
49
+ */
20
50
  async enablePlugin(pluginId) {
21
51
  await browser.executeObsidian(
22
52
  async ({ app }, pluginId2) => await app.plugins.enablePluginAndSave(pluginId2),
23
53
  pluginId
24
54
  );
25
55
  }
26
- /** Disables a plugin */
56
+ /**
57
+ * Disables a plugin by ID
58
+ */
27
59
  async disablePlugin(pluginId) {
28
60
  await browser.executeObsidian(
29
61
  async ({ app }, pluginId2) => await app.plugins.disablePluginAndSave(pluginId2),
30
62
  pluginId
31
63
  );
32
64
  }
33
- /** Sets the theme. Pass "default" to reset to the Obsidian theme. */
65
+ /**
66
+ * Sets the theme. Pass "default" to reset to the Obsidian theme.
67
+ */
34
68
  async setTheme(themeName) {
35
69
  themeName = themeName == "default" ? "" : themeName;
36
70
  await browser.executeObsidian(
@@ -52,35 +86,124 @@ var ObsidianPage = class {
52
86
  }, path3);
53
87
  }
54
88
  /**
55
- * Loads a saved workspace from `workspaces.json` by name. You can use the core "Workspaces" plugin to create the
56
- * layouts.
89
+ * Loads a saved workspace layout from `.obsidian/workspaces.json` by name. Use the core "Workspaces"
90
+ * plugin to create the layouts. You can also pass the layout object directly.
57
91
  */
58
- async loadWorkspaceLayout(layoutName) {
59
- const vaultPath = await browser.getVaultPath();
60
- const workspacesPath = path.join(vaultPath, ".obsidian/workspaces.json");
61
- let layout = void 0;
62
- try {
63
- layout = await fsAsync.readFile(workspacesPath, "utf-8");
64
- } catch {
65
- throw new Error(`No workspace ${layout} found in .obsidian/workspaces.json`);
92
+ async loadWorkspaceLayout(layout) {
93
+ if (typeof layout == "string") {
94
+ const vaultPath = await this.getVaultPath();
95
+ const workspacesPath = path.join(vaultPath, ".obsidian/workspaces.json");
96
+ const layoutName = layout;
97
+ try {
98
+ const fileContent = await fsAsync.readFile(workspacesPath, "utf-8");
99
+ layout = JSON.parse(fileContent)?.workspaces?.[layoutName];
100
+ } catch {
101
+ throw new Error(`No workspace ${layoutName} found in .obsidian/workspaces.json`);
102
+ }
66
103
  }
67
- layout = JSON.parse(layout).workspaces?.[layoutName];
68
104
  await $(".status-bar").click();
69
105
  await browser.executeObsidian(async ({ app }, layout2) => {
70
106
  await app.workspace.changeLayout(layout2);
71
107
  }, layout);
72
108
  }
109
+ /**
110
+ * Resets the vault files to the original state by deleting/creating/modifying vault files in place without
111
+ * reloading Obsidian.
112
+ *
113
+ * This will only reset regular vault files, it won't touch anything under `.obsidian`, and it won't reset any
114
+ * config and app state you've set in Obsidian. But if all you need is to reset the vault files, this can be used as
115
+ * a faster alternative to reloadObsidian.
116
+ *
117
+ * If no vault is passed, it resets the vault back to the oringal vault opened by the tests. You can also pass a
118
+ * path to an different vault, and it will sync the current vault to match that one (similar to "rsync"). Or,
119
+ * instead of passing a vault path you can pass an object mapping vault file paths to file content. E.g.
120
+ * ```ts
121
+ * obsidianPage.resetVault({
122
+ * 'path/in/vault.md': "Hello World",
123
+ * })
124
+ * ```
125
+ *
126
+ * You can also pass multiple vaults and the files will be merged. This can be useful if you want to add a few small
127
+ * modifications to the base vault. e.g:
128
+ * ```ts
129
+ * obsidianPage.resetVault('./path/to/vault', {
130
+ * "books/leviathan-wakes.md": "...",
131
+ * })
132
+ * ```
133
+ */
134
+ async resetVaultFiles(...vaults) {
135
+ const origVaultPath = browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].vault;
136
+ vaults = vaults.length == 0 ? [origVaultPath] : vaults;
137
+ const newVaultFiles = {};
138
+ for (let vault of vaults) {
139
+ if (typeof vault == "string") {
140
+ vault = path.resolve(vault);
141
+ const files = await fsAsync.readdir(vault, { recursive: true, withFileTypes: true });
142
+ for (const file of files) {
143
+ const absPath = path.join(file.parentPath, file.name);
144
+ const obsPath = path.relative(vault, absPath).split(path.sep).join("/");
145
+ if (file.isFile() && !obsPath.startsWith(".obsidian/")) {
146
+ newVaultFiles[obsPath] = { path: absPath };
147
+ }
148
+ }
149
+ } else {
150
+ for (const [file, content] of Object.entries(vault)) {
151
+ newVaultFiles[file] = { content };
152
+ }
153
+ }
154
+ }
155
+ const newVaultFolders = _(newVaultFiles).keys().map((p) => path.dirname(p)).filter((p) => p != ".").sort().uniq().value();
156
+ await browser.executeObsidian(async ({ app, obsidian }, newVaultFolders2, newVaultFiles2) => {
157
+ const fs2 = __require("fs");
158
+ for (const newFolder of newVaultFolders2) {
159
+ let currFile = app.vault.getAbstractFileByPath(newFolder);
160
+ if (currFile && currFile instanceof obsidian.TFile) {
161
+ await app.vault.delete(currFile);
162
+ currFile = null;
163
+ }
164
+ if (!currFile) {
165
+ await app.vault.createFolder(newFolder);
166
+ }
167
+ }
168
+ const currVaultFolders = app.vault.getAllLoadedFiles().filter((f) => f instanceof obsidian.TFolder).sort((a, b) => b.path.localeCompare(a.path));
169
+ const newVaultFoldersSet = new Set(newVaultFolders2);
170
+ for (const currFolder of currVaultFolders) {
171
+ if (!newVaultFoldersSet.has(currFolder.path)) {
172
+ await app.vault.delete(currFolder, true);
173
+ }
174
+ }
175
+ for (let [newFilePath, newFileSource] of Object.entries(newVaultFiles2)) {
176
+ let currFile = app.vault.getAbstractFileByPath(newFilePath);
177
+ const content = newFileSource.content ?? await fs2.readFileSync(newFileSource.path, "utf-8");
178
+ if (currFile) {
179
+ await app.vault.modify(currFile, content);
180
+ } else {
181
+ await app.vault.create(newFilePath, content);
182
+ }
183
+ }
184
+ const newVaultFilesSet = new Set(Object.keys(newVaultFiles2));
185
+ for (const currVaultFile of app.vault.getFiles()) {
186
+ if (!newVaultFilesSet.has(currVaultFile.path)) {
187
+ await app.vault.delete(currVaultFile);
188
+ }
189
+ }
190
+ }, newVaultFolders, newVaultFiles);
191
+ }
73
192
  };
74
193
  var obsidianPage = new ObsidianPage();
75
194
  var obsidianPage_default = obsidianPage;
76
195
 
77
196
  // src/browserCommands.ts
78
197
  var browserCommands = {
79
- /** Returns the Obsidian version this test is running under. */
198
+ /**
199
+ * Returns the Obsidian app version this test is running under.
200
+ */
80
201
  async getObsidianVersion() {
81
202
  return this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].appVersion;
82
203
  },
83
- /** Returns the Obsidian installer version this test is running under. */
204
+ /**
205
+ * Returns the Obsidian installer version this test is running under.
206
+ */
84
207
  async getObsidianInstallerVersion() {
85
208
  return this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].installerVersion;
86
209
  },
@@ -89,12 +212,14 @@ var browserCommands = {
89
212
  * is an object containing keys:
90
213
  * - app: Obsidian app instance
91
214
  * - obsidian: Full Obsidian API
215
+ * - plugins: Object of all installed plugins, mapped by plugin id converted to camelCase.
216
+ *
92
217
  * See also: https://webdriver.io/docs/api/browser/execute
93
218
  *
94
219
  * Example usage
95
220
  * ```ts
96
221
  * const file = browser.executeObsidian(({app, obsidian}, path) => {
97
- * return app.vault.getMarkdownFiles().find(f => f.path == path)
222
+ * return app.vault.getMarkdownFiles().find(f => f.path == path);
98
223
  * })
99
224
  * ```
100
225
  *
@@ -121,7 +246,7 @@ var browserCommands = {
121
246
  */
122
247
  async executeObsidian(func, ...params) {
123
248
  return await browser.execute(
124
- `return (${func.toString()}).call(null, {...window.wdioObsidianService}, ...arguments )`,
249
+ `return (${func.toString()}).call(null, {...window.wdioObsidianService}, ...arguments)`,
125
250
  ...params
126
251
  );
127
252
  },
@@ -135,24 +260,9 @@ var browserCommands = {
135
260
  throw Error(`Obsidian command ${id} not found or failed.`);
136
261
  }
137
262
  },
138
- /** Returns the path to the vault opened in Obsidian */
139
- async getVaultPath() {
140
- if (this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].vault == void 0) {
141
- return void 0;
142
- } else {
143
- return await this.executeObsidian(({ app, obsidian }) => {
144
- if (app.vault.adapter instanceof obsidian.FileSystemAdapter) {
145
- return app.vault.adapter.getBasePath();
146
- } else {
147
- throw new Error(`Unrecognized DataAdapater type`);
148
- }
149
- ;
150
- });
151
- }
152
- },
153
263
  /**
154
264
  * Returns the Workspace page object with convenience helper functions.
155
- * You can also import the page object directly with
265
+ * You can also just import the page object directly with
156
266
  * ```ts
157
267
  * import { obsidianPage } from "wdio-obsidian-service"
158
268
  * ```
@@ -165,17 +275,17 @@ var browserCommands_default = browserCommands;
165
275
 
166
276
  // src/utils.ts
167
277
  async function sleep(ms) {
168
- return new Promise((resolve) => setTimeout(resolve, ms));
278
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
169
279
  }
170
280
 
171
281
  // src/service.ts
172
282
  import semver from "semver";
173
- import _ from "lodash";
283
+ import _2 from "lodash";
174
284
  var log = logger("wdio-obsidian-service");
175
285
  function getDefaultCacheDir() {
176
- return path2.resolve(process.env.WEBDRIVER_CACHE_DIR ?? "./.obsidian-cache");
286
+ return path2.resolve(process.env.WEBDRIVER_CACHE_DIR ?? process.env.OBSIDIAN_CACHE ?? "./.obsidian-cache");
177
287
  }
178
- var minSupportedObsidianVersion = "1.0.2";
288
+ var minSupportedObsidianVersion = "1.0.3";
179
289
  var ObsidianLauncherService = class {
180
290
  constructor(options, capabilities, config) {
181
291
  this.options = options;
@@ -189,6 +299,9 @@ var ObsidianLauncherService = class {
189
299
  });
190
300
  this.helperPluginPath = path2.resolve(path2.join(fileURLToPath(import.meta.url), "../../helper-plugin"));
191
301
  }
302
+ /**
303
+ * Validates wdio:obsidianOptions and downloads Obsidian, plugins, and themes.
304
+ */
192
305
  async onPrepare(config, capabilities) {
193
306
  if (!Array.isArray(capabilities)) {
194
307
  capabilities = Object.values(capabilities).map(
@@ -230,6 +343,9 @@ var ObsidianLauncherService = class {
230
343
  plugins.push(this.helperPluginPath);
231
344
  plugins = await this.obsidianLauncher.downloadPlugins(plugins);
232
345
  const themes = await this.obsidianLauncher.downloadThemes(obsidianOptions.themes ?? []);
346
+ if (obsidianOptions.vault != void 0 && !fs.existsSync(obsidianOptions.vault)) {
347
+ throw Error(`Vault "${obsidianOptions.vault}" doesn't exist`);
348
+ }
233
349
  const args = [
234
350
  // Workaround for SUID issue on AppImages. See https://github.com/electron/electron/issues/42510
235
351
  ...process.platform == "linux" ? ["--no-sandbox"] : [],
@@ -283,6 +399,9 @@ var ObsidianWorkerService = class {
283
399
  });
284
400
  this.tmpDirs = [];
285
401
  }
402
+ /**
403
+ * Setup vault and config dir for a sandboxed Obsidian instance.
404
+ */
286
405
  async setupObsidian(obsidianOptions) {
287
406
  let vault = obsidianOptions.vault;
288
407
  if (vault != void 0) {
@@ -306,6 +425,9 @@ var ObsidianWorkerService = class {
306
425
  this.tmpDirs.push(configDir);
307
426
  return configDir;
308
427
  }
428
+ /**
429
+ * Wait for Obsidian to fully boot.
430
+ */
309
431
  async waitForReady(browser2) {
310
432
  if (browser2.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].vault != void 0) {
311
433
  await browser2.waitUntil(
@@ -314,10 +436,13 @@ var ObsidianWorkerService = class {
314
436
  { timeout: 30 * 1e3, interval: 100 }
315
437
  );
316
438
  await browser2.executeObsidian(async ({ app }) => {
317
- await new Promise((resolve) => app.workspace.onLayoutReady(resolve));
439
+ await new Promise((resolve2) => app.workspace.onLayoutReady(resolve2));
318
440
  });
319
441
  }
320
442
  }
443
+ /**
444
+ * Handles vault and sandboxed config directory setup.
445
+ */
321
446
  async beforeSession(config, capabilities) {
322
447
  if (!capabilities[OBSIDIAN_CAPABILITY_KEY]) return;
323
448
  const configDir = await this.setupObsidian(capabilities[OBSIDIAN_CAPABILITY_KEY]);
@@ -326,9 +451,12 @@ var ObsidianWorkerService = class {
326
451
  ...capabilities["goog:chromeOptions"].args ?? []
327
452
  ];
328
453
  }
454
+ /**
455
+ * Returns a plugin list with only the selected plugin ids enabled.
456
+ */
329
457
  selectPlugins(currentPlugins, selection) {
330
458
  if (selection !== void 0) {
331
- const unknownPlugins = _.difference(selection, currentPlugins.map((p) => p.id));
459
+ const unknownPlugins = _2.difference(selection, currentPlugins.map((p) => p.id));
332
460
  if (unknownPlugins.length > 0) {
333
461
  throw Error(`Unknown plugin ids: ${unknownPlugins.join(", ")}`);
334
462
  }
@@ -340,6 +468,9 @@ var ObsidianWorkerService = class {
340
468
  return currentPlugins;
341
469
  }
342
470
  }
471
+ /**
472
+ * Returns a theme list with only the selected theme enabled.
473
+ */
343
474
  selectThemes(currentThemes, selection) {
344
475
  if (selection !== void 0) {
345
476
  if (selection != "default" && currentThemes.every((t) => t.name != selection)) {
@@ -350,6 +481,9 @@ var ObsidianWorkerService = class {
350
481
  return currentThemes;
351
482
  }
352
483
  }
484
+ /**
485
+ * Setup custom browser commands.
486
+ */
353
487
  async before(capabilities, specs, browser2) {
354
488
  browser2.setMaxListeners(1e3);
355
489
  if (!capabilities[OBSIDIAN_CAPABILITY_KEY]) return;
@@ -385,10 +519,10 @@ var ObsidianWorkerService = class {
385
519
  const enabledPlugins = await browser2.executeObsidian(
386
520
  ({ app }) => [...app.plugins.enabledPlugins].sort()
387
521
  );
388
- for (const pluginId of _.difference(enabledPlugins, plugins, ["wdio-obsidian-service-plugin"])) {
522
+ for (const pluginId of _2.difference(enabledPlugins, plugins, ["wdio-obsidian-service-plugin"])) {
389
523
  await obsidianPage_default.disablePlugin(pluginId);
390
524
  }
391
- for (const pluginId of _.difference(plugins, enabledPlugins)) {
525
+ for (const pluginId of _2.difference(plugins, enabledPlugins)) {
392
526
  await obsidianPage_default.enablePlugin(pluginId);
393
527
  }
394
528
  }
@@ -403,7 +537,7 @@ var ObsidianWorkerService = class {
403
537
  }
404
538
  const sessionId = await browser2.reloadSession({
405
539
  // if browserName is set, reloadSession tries to restart the driver entirely, so unset those
406
- ..._.omit(this.requestedCapabilities, ["browserName", "browserVersion"]),
540
+ ..._2.omit(this.requestedCapabilities, ["browserName", "browserVersion"]),
407
541
  ...newCapabilities
408
542
  });
409
543
  await service.waitForReady(this);
@@ -415,6 +549,9 @@ var ObsidianWorkerService = class {
415
549
  }
416
550
  await service.waitForReady(browser2);
417
551
  }
552
+ /**
553
+ * Cleanup
554
+ */
418
555
  async afterSession() {
419
556
  for (const tmpDir of this.tmpDirs) {
420
557
  await fsAsync2.rm(tmpDir, { recursive: true, force: true });
@@ -437,6 +574,7 @@ async function resolveObsidianVersions(appVersion, installerVersion, cacheDir) {
437
574
  export {
438
575
  index_default as default,
439
576
  launcher,
577
+ minSupportedObsidianVersion,
440
578
  obsidianBetaAvailable,
441
579
  obsidianPage_default as obsidianPage,
442
580
  resolveObsidianVersions
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/service.ts","../src/types.ts","../src/pageobjects/obsidianPage.ts","../src/browserCommands.ts","../src/utils.ts"],"sourcesContent":["import ObsidianLauncher from \"obsidian-launcher\";\nimport { ObsidianLauncherService, ObsidianWorkerService } from \"./service.js\";\n\nexport default ObsidianWorkerService;\nexport const launcher = ObsidianLauncherService;\n\nexport type { ObsidianServiceOptions, ObsidianCapabilityOptions } from \"./types.js\";\n\nexport { default as obsidianPage } from \"./pageobjects/obsidianPage.js\";\n\nexport type {\n LocalPluginEntry, GitHubPluginEntry, CommunityPluginEntry, PluginEntry,\n LocalThemeEntry, GitHubThemeEntry, CommunityThemeEntry, ThemeEntry,\n} from \"obsidian-launcher\";\n\n\n// Some convenience helpers for use in wdio.conf.ts\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 */\nexport async function obsidianBetaAvailable(cacheDir?: string) {\n const launcher = new ObsidianLauncher({cacheDir: cacheDir});\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 * @param appVersion Obsidian version string or \"latest\", \"latest-beta\" or \"earliest\". \"earliest\" will use the \n * minAppVersion set in your manifest.json.\n * @param installerVersion Obsidian version string or \"latest\" or \"earliest\". \"earliest\" will use the minimum\n * installer version compatible with the appVersion.\n * @returns [appVersion, installerVersion] with any \"latest\" etc. resolved to specific versions.\n */\nexport async function resolveObsidianVersions(\n appVersion: string, installerVersion: string, cacheDir?: string,\n): Promise<[string, string]> {\n const launcher = new ObsidianLauncher({cacheDir: cacheDir});\n return await launcher.resolveVersions(appVersion, installerVersion);\n\n}\n","import 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, { DownloadedPluginEntry, DownloadedThemeEntry } from \"obsidian-launcher\"\nimport browserCommands from \"./browserCommands.js\"\nimport { ObsidianCapabilityOptions, ObsidianServiceOptions, OBSIDIAN_CAPABILITY_KEY } from \"./types.js\"\nimport obsidianPage from \"./pageobjects/obsidianPage.js\"\nimport { sleep } from \"./utils.js\"\nimport semver from \"semver\"\nimport _ from \"lodash\"\n\n\nconst log = logger(\"wdio-obsidian-service\");\n\nfunction getDefaultCacheDir() {\n return path.resolve(process.env.WEBDRIVER_CACHE_DIR ?? \"./.obsidian-cache\")\n}\n\n/**\n * Minimum Obsidian version that wdio-obsidian-service supports.\n */\nexport const minSupportedObsidianVersion: string = \"1.0.2\"\n\n\nexport class ObsidianLauncherService implements Services.ServiceInstance {\n private obsidianLauncher: ObsidianLauncher\n private readonly helperPluginPath: string\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(),\n versionsUrl: options.versionsUrl,\n communityPluginsUrl: options.communityPluginsUrl, communityThemesUrl: options.communityThemesUrl,\n });\n this.helperPluginPath = path.resolve(path.join(fileURLToPath(import.meta.url), '../../helper-plugin'));\n }\n\n async onPrepare(config: Options.Testrunner, capabilities: Capabilities.TestrunnerCapabilities) {\n if (!Array.isArray(capabilities)) {\n capabilities = Object.values(capabilities as Capabilities.RequestedMultiremoteCapabilities).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 try {\n for (const cap of obsidianCapabilities) {\n const obsidianOptions = cap[OBSIDIAN_CAPABILITY_KEY] ?? {};\n \n const vault = obsidianOptions.vault != undefined ? path.resolve(obsidianOptions.vault) : undefined;\n \n const [appVersion, installerVersion] = await this.obsidianLauncher.resolveVersions(\n cap.browserVersion ?? cap[OBSIDIAN_CAPABILITY_KEY]?.appVersion ?? \"latest\",\n obsidianOptions.installerVersion ?? \"earliest\",\n );\n const installerVersionInfo = await this.obsidianLauncher.getVersionInfo(installerVersion);\n if (semver.lt(appVersion, minSupportedObsidianVersion)) {\n throw Error(`Minimum supported Obsidian version is ${minSupportedObsidianVersion}`)\n }\n\n let installerPath = obsidianOptions.binaryPath;\n if (!installerPath) {\n installerPath = await this.obsidianLauncher.downloadInstaller(installerVersion);\n }\n let appPath = obsidianOptions.appPath;\n if (!appPath) {\n appPath = await this.obsidianLauncher.downloadApp(appVersion);\n }\n let chromedriverPath = cap['wdio:chromedriverOptions']?.binary\n // wdio can download chromedriver for versions greater than 115 automatically\n if (!chromedriverPath && Number(installerVersionInfo.chromeVersion!.split(\".\")[0]) <= 115) {\n chromedriverPath = await this.obsidianLauncher.downloadChromedriver(installerVersion);\n }\n\n let plugins = obsidianOptions.plugins ?? [\".\"];\n plugins.push(this.helperPluginPath); // Always install the helper plugin\n plugins = await this.obsidianLauncher.downloadPlugins(plugins);\n\n const themes = await this.obsidianLauncher.downloadThemes(obsidianOptions.themes ?? []);\n\n const args = [\n // Workaround for SUID issue on AppImages. See https://github.com/electron/electron/issues/42510\n ...(process.platform == 'linux' ? [\"--no-sandbox\"] : []),\n ...(cap['goog:chromeOptions']?.args ?? [])\n ];\n\n cap.browserName = \"chrome\";\n cap.browserVersion = installerVersionInfo.chromeVersion;\n cap[OBSIDIAN_CAPABILITY_KEY] = {\n ...obsidianOptions,\n plugins: plugins,\n themes: themes,\n binaryPath: installerPath,\n appPath: appPath,\n vault: vault,\n appVersion: appVersion, // Resolve the versions\n installerVersion: installerVersion,\n };\n cap['goog:chromeOptions'] = {\n binary: installerPath,\n windowTypes: [\"app\", \"webview\"],\n ...cap['goog:chromeOptions'],\n args: args,\n }\n cap['wdio:chromedriverOptions'] = {\n // allowedIps is not included in the types, but gets passed as --allowed-ips to chromedriver.\n // It defaults to [\"0.0.0.0\"] which makes Windows Firewall complain, and we don't need remote\n // connections anyways.\n allowedIps: [],\n ...cap['wdio:chromedriverOptions'],\n binary: chromedriverPath,\n } as any\n cap[\"wdio:enforceWebDriverClassic\"] = true;\n }\n } catch (e: any) {\n // By default wdio just logs service errors, throwing this makes it bail if anything goes wrong.\n throw new SevereServiceError(`Failed to download and setup Obsidian. Caused by: ${e.stack}\\n`+\n ` ------The above causes:-----`);\n }\n }\n}\n\nexport class ObsidianWorkerService implements Services.ServiceInstance {\n private obsidianLauncher: ObsidianLauncher\n /** Directories to clean up after the tests */\n private tmpDirs: string[]\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(),\n versionsUrl: options.versionsUrl,\n communityPluginsUrl: options.communityPluginsUrl, communityThemesUrl: options.communityThemesUrl,\n });\n this.tmpDirs = [];\n }\n\n private async setupObsidian(obsidianOptions: ObsidianCapabilityOptions) {\n let vault = obsidianOptions.vault;\n if (vault != undefined) {\n log.info(`Opening vault ${obsidianOptions.vault}`);\n vault = await this.obsidianLauncher.setupVault({\n vault,\n copy: true,\n plugins: obsidianOptions.plugins,\n themes: obsidianOptions.themes,\n });\n this.tmpDirs.push(vault);\n } else {\n log.info(`Opening Obsidian without a vault`)\n }\n\n const configDir = await this.obsidianLauncher.setupConfigDir({\n appVersion: obsidianOptions.appVersion!, installerVersion: obsidianOptions.installerVersion!,\n appPath: obsidianOptions.appPath!,\n vault: vault,\n });\n this.tmpDirs.push(configDir);\n\n return configDir;\n }\n\n private async waitForReady(browser: WebdriverIO.Browser) {\n if (browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].vault != undefined) {\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 }\n }\n\n async beforeSession(config: Options.Testrunner, capabilities: WebdriverIO.Capabilities) {\n if (!capabilities[OBSIDIAN_CAPABILITY_KEY]) return;\n\n const configDir = await this.setupObsidian(capabilities[OBSIDIAN_CAPABILITY_KEY]);\n\n capabilities['goog:chromeOptions']!.args = [\n `--user-data-dir=${configDir}`,\n ...(capabilities['goog:chromeOptions']!.args ?? [])\n ];\n }\n\n private selectPlugins(currentPlugins: DownloadedPluginEntry[], selection?: string[]) {\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 private selectThemes(currentThemes: DownloadedThemeEntry[], selection?: string) {\n if (selection !== undefined) {\n if (selection != \"default\" && currentThemes.every((t: any) => t.name != selection)) {\n throw Error(`Unknown theme: ${selection}`);\n }\n return currentThemes.map((t: any) => ({...t, enabled: selection != 'default' && t.name === selection}));\n } else {\n return currentThemes;\n }\n }\n\n async before(capabilities: WebdriverIO.Capabilities, specs: never, browser: WebdriverIO.Browser) {\n // There's a slow event listener link on the browser \"command\" event when you reloadSession that causes some\n // warnings. This will silence them. TODO: Make issue or PR to wdio to fix this.\n browser.setMaxListeners(1000);\n\n if (!capabilities[OBSIDIAN_CAPABILITY_KEY]) return;\n\n const service = this; // eslint-disable-line @typescript-eslint/no-this-alias\n const reloadObsidian: typeof browser['reloadObsidian'] = async function(\n this: WebdriverIO.Browser,\n {vault, plugins, theme} = {},\n ) {\n const oldObsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];\n let newCapabilities: WebdriverIO.Capabilities\n\n if (vault) {\n const newObsidianOptions = {\n ...oldObsidianOptions,\n vault: path.resolve(vault),\n plugins: service.selectPlugins(oldObsidianOptions.plugins, plugins),\n themes: service.selectThemes(oldObsidianOptions.themes, theme),\n }\n \n const configDir = await service.setupObsidian(newObsidianOptions);\n \n const newArgs = [\n `--user-data-dir=${configDir}`,\n ...this.requestedCapabilities['goog:chromeOptions'].args.filter((arg: string) => {\n const match = arg.match(/^--user-data-dir=(.*)$/);\n return !match || !service.tmpDirs.includes(match[1]);\n }),\n ]\n \n newCapabilities = {\n [OBSIDIAN_CAPABILITY_KEY]: newObsidianOptions,\n 'goog:chromeOptions': {\n ...this.requestedCapabilities['goog:chromeOptions'],\n args: newArgs,\n },\n };\n } else {\n // preserve vault and config dir\n newCapabilities = {};\n // Since we aren't recreating the vault, we'll need to reset plugins and themes here if specified.\n if (plugins) {\n const enabledPlugins = await browser.executeObsidian(({app}) =>\n [...(app as any).plugins.enabledPlugins].sort()\n )\n for (const pluginId of _.difference(enabledPlugins, plugins, ['wdio-obsidian-service-plugin'])) {\n await obsidianPage.disablePlugin(pluginId);\n }\n for (const pluginId of _.difference(plugins, enabledPlugins)) {\n await obsidianPage.enablePlugin(pluginId);\n }\n }\n if (theme) {\n await obsidianPage.setTheme(theme);\n }\n // Obsidian debounces saves to the config dir, and so changes to configuration made in the tests may not\n // get saved to disk before the reboot. Here I manually trigger save for plugins and themes, but other\n // configurations might not get saved. I haven't found a better way to flush everything than just\n // waiting a bit. Wdio has ways to mock the clock, which might work, but it's only supported when using\n // BiDi, which I can't get working on Obsidian.\n await browser.executeObsidian(async ({app}) => await Promise.all([\n (app as any).plugins.saveConfig(),\n (app.vault as any).saveConfig(),\n ]))\n await sleep(2000);\n }\n\n const sessionId = await browser.reloadSession({\n // if browserName is set, reloadSession tries to restart the driver entirely, so unset those\n ..._.omit(this.requestedCapabilities, ['browserName', 'browserVersion']),\n ...newCapabilities,\n });\n await service.waitForReady(this);\n return sessionId;\n }\n\n await browser.addCommand(\"reloadObsidian\", reloadObsidian);\n\n for (const [name, cmd] of Object.entries(browserCommands)) {\n await browser.addCommand(name, cmd);\n }\n\n await service.waitForReady(browser);\n }\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 type { ObsidianBrowserCommands } from \"./browserCommands.js\";\nimport type { PluginEntry, ThemeEntry } from \"obsidian-launcher\";\n\nexport const OBSIDIAN_CAPABILITY_KEY = \"wdio:obsidianOptions\" as const;\n\nexport interface ObsidianServiceOptions {\n /**\n * Override the `obsidian-versions.json` used by the service. Can be a file URL.\n * This is only really useful for this package's own internal tests.\n */\n versionsUrl?: string,\n /**\n * Override the `community-plugins.json` used by the service. Can be a file URL.\n * This is only really useful for this package's own internal tests.\n */\n communityPluginsUrl?: string,\n /**\n * Override the `community-css-themes.json` used by the service. Can be a file URL.\n * This is only really useful for this package's own internal tests.\n */\n communityThemesUrl?: string,\n}\n\nexport interface ObsidianCapabilityOptions {\n /**\n * Version of Obsidian to run.\n * \n * Can be set to \"latest\", \"latest-beta\", or a specific version. Defaults to \"latest\". To download beta versions\n * you'll need to be an Obsidian insider and set the OBSIDIAN_USERNAME and OBSIDIAN_PASSWORD environment\n * variables.\n * \n * You can also use the wdio capability `browserVersion` field to set the Obsidian version.\n */\n appVersion?: string\n\n /**\n * Version of the Obsidian Installer to download.\n * \n * Note that Obsidian is distributed in two parts, the \"installer\", which is the executable containing Electron, and\n * the \"app\" which is a bundle of JavaScript containing the Obsidian code. Obsidian's self-update system only\n * updates the JavaScript bundle, and not the base installer/Electron version. This makes Obsidian's auto-update\n * fast as it only needs to download a few MiB of JS instead of all of Electron. But, it means different users with\n * the same Obsidian version may be running on different versions of Electron, which could cause subtle differences\n * in plugin behavior if you are using newer JavaScript features and the like in your plugin.\n * \n * If passed \"latest\", it will use the maximum installer version compatible with the selected Obsidian version. If\n * passed \"earliest\" it will use the oldest installer version compatible with the selected Obsidian version. The \n * default is \"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. You\n * can also pass objects. If you pass an object it can contain one of either `path` (to install a local plugin),\n * `repo` (to install a plugin from github), or `id` (to install a community plugin). You can set `enabled: false`\n * to install the plugin but start it disabled. You can enable the plugin later using `reloadObsidian` or the\n * `enablePlugin` command.\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. You can also pass an object. If you pass an object it can\n * contain one of either `path` (to install a local theme), `repo` (to install a theme from github), or `name` (to\n * install a community theme). You can set `enabled: false` to install the theme, but start it disabled. You can\n * only have one enabled theme, so if you pass multiple you'll have to disable all 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 `browser.reloadObsidian` to open a vault during your tests.\n */\n vault?: string,\n\n /** Path to the Obsidian binary to use. If omitted it will download Obsidian automatically. */\n binaryPath?: string,\n\n /** Path to the app asar to load into obsidian. If omitted it will be downloaded automatically. */\n appPath?: string,\n}\n\n\ndeclare global {\n namespace WebdriverIO {\n interface Capabilities {\n [OBSIDIAN_CAPABILITY_KEY]?: ObsidianCapabilityOptions,\n }\n\n interface Browser extends ObsidianBrowserCommands {\n /**\n * Relaunch obsidian. Can be used to switch to a new vault, change the plugin list, or just to reboot\n * Obsidian.\n * \n * As this does a full reboot of Obsidian, avoid calling this too often so you don't slow your tests down.\n * You can also set the vault in the `wdio.conf.ts` capabilities section which may be useful if all\n * your tests use the same vault.\n * \n * @param 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\n * creating a new copy of the vault.\n * @param 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.ts capabilities. You can also use the enablePlugin and \n * disablePlugin commands to change plugins without relaunching Obsidian.\n * @param 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.ts.\n * @returns Returns the new sessionId (same as browser.reloadSession()).\n */\n reloadObsidian(params?: {\n vault?: string,\n plugins?: string[], theme?: string,\n }): Promise<string>;\n }\n }\n}\n","import * as path from \"path\"\nimport * as fsAsync from \"fs/promises\"\n\n/**\n * Class with various helper methods for writing Obsidian tests using the\n * [page object pattern](https://webdriver.io/docs/pageobjects).\n */\nclass ObsidianPage {\n /** Enables a plugin */\n async enablePlugin(pluginId: string): Promise<void> {\n await browser.executeObsidian(\n async ({app}, pluginId) => await (app as any).plugins.enablePluginAndSave(pluginId),\n pluginId,\n );\n }\n\n /** Disables a plugin */\n async disablePlugin(pluginId: string): Promise<void> {\n await browser.executeObsidian(\n async ({app}, pluginId) => await (app as any).plugins.disablePluginAndSave(pluginId),\n pluginId,\n );\n }\n\n /** Sets the theme. Pass \"default\" to reset to the Obsidian theme. */\n async setTheme(themeName: string): Promise<void> {\n themeName = themeName == 'default' ? '' : themeName;\n await browser.executeObsidian(\n async ({app}, themeName) => await (app as any).customCss.setTheme(themeName),\n themeName,\n )\n }\n\n /**\n * Opens a file in a new tab.\n */\n async openFile(path: string) {\n await browser.executeObsidian(async ({app, obsidian}, path) => {\n const file = app.vault.getAbstractFileByPath(path);\n if (file instanceof obsidian.TFile) {\n await app.workspace.getLeaf('tab').openFile(file);\n } else {\n throw Error(`No file ${path} exists`);\n }\n }, path)\n }\n\n /**\n * Loads a saved workspace from `workspaces.json` by name. You can use the core \"Workspaces\" plugin to create the\n * layouts.\n */\n async loadWorkspaceLayout(layoutName: string): Promise<void> {\n // read from .obsidian/workspaces.json like the built-in workspaces plugin does\n const vaultPath = (await browser.getVaultPath())!;\n const workspacesPath = path.join(vaultPath, '.obsidian/workspaces.json');\n \n let layout: any = undefined\n try {\n layout = await fsAsync.readFile(workspacesPath, 'utf-8')\n } catch {\n throw new Error(`No workspace ${layout} found in .obsidian/workspaces.json`);\n }\n layout = JSON.parse(layout).workspaces?.[layoutName];\n \n // Click on the status-bar to focus the main window in case there are multiple Obsidian windows panes\n await $(\".status-bar\").click();\n await browser.executeObsidian(async ({app}, layout) => {\n await app.workspace.changeLayout(layout)\n }, layout)\n }\n \n}\n\nconst obsidianPage = new ObsidianPage()\nexport default obsidianPage;\nexport { ObsidianPage };\n","import { OBSIDIAN_CAPABILITY_KEY } from \"./types.js\";\nimport type * as obsidian from \"obsidian\"\nimport obsidianPage, { ObsidianPage } from \"./pageobjects/obsidianPage.js\"\n\n\ntype 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\nconst browserCommands = {\n /** Returns the Obsidian version this test is running under. */\n async getObsidianVersion(this: WebdriverIO.Browser): Promise<string> {\n return this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].appVersion;\n },\n\n /** Returns the Obsidian installer version this test is running under. */\n async getObsidianInstallerVersion(this: WebdriverIO.Browser): Promise<string> {\n return this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].installerVersion;\n },\n\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 * See also: https://webdriver.io/docs/api/browser/execute\n * \n * Example usage\n * ```ts\n * const file = browser.executeObsidian(({app, obsidian}, path) => {\n * return app.vault.getMarkdownFiles().find(f => f.path == path)\n * })\n * ```\n * \n * Note: The same caveats as `browser.execute` apply. The function will be stringified and then run inside Obsidian,\n * so you can't capture any local variables. E.g.\n * \n * This *won't* work:\n * ```ts\n * import { FileView } from Obsidian\n * browser.executeObsidian(({app, obsidian}) => {\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 func: (obs: ExecuteObsidianArg, ...params: Params) => Return,\n ...params: Params\n ): Promise<Return> {\n return await browser.execute<Return, Params>(\n `return (${func.toString()}).call(null, {...window.wdioObsidianService}, ...arguments )`,\n ...params,\n )\n },\n\n /**\n * Executes an Obsidian command by id.\n * @param id Id of the command to run.\n */\n async executeObsidianCommand(this: WebdriverIO.Browser, id: string) {\n const result = await this.executeObsidian(({app}, id) => (app as any).commands.executeCommandById(id), id);\n if (!result) {\n throw Error(`Obsidian command ${id} not found or failed.`);\n }\n },\n\n /** Returns the path to the vault opened in Obsidian */\n async getVaultPath(this: WebdriverIO.Browser): Promise<string|undefined> {\n if (this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].vault == undefined) {\n return undefined; // no vault open\n } else { // return the actual path to the vault\n return await this.executeObsidian(({app, obsidian}) => {\n if (app.vault.adapter instanceof obsidian.FileSystemAdapter) {\n return app.vault.adapter.getBasePath()\n } else { // TODO handle CapacitorAdapater\n throw new Error(`Unrecognized DataAdapater type`)\n };\n })\n }\n },\n\n /**\n * Returns the Workspace page object with convenience helper functions.\n * You can also import the page object directly with\n * ```ts\n * import { obsidianPage } from \"wdio-obsidian-service\"\n * ```\n */\n async getObsidianPage(this: WebdriverIO.Browser): Promise<ObsidianPage> {\n return obsidianPage;\n }\n} as const\n\nexport type ObsidianBrowserCommands = typeof browserCommands;\nexport default browserCommands;\n","export async function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}"],"mappings":"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","names":["ObsidianLauncher","fsAsync","path","pluginId","themeName","path","layout","id","path","browser","fsAsync","launcher","ObsidianLauncher"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/service.ts","../src/types.ts","../src/pageobjects/obsidianPage.ts","../src/browserCommands.ts","../src/utils.ts"],"sourcesContent":["import ObsidianLauncher from \"obsidian-launcher\";\nimport { ObsidianLauncherService, ObsidianWorkerService } from \"./service.js\";\n/** @hidden */\nexport default ObsidianWorkerService;\n/** @hidden */\nexport const launcher = ObsidianLauncherService;\nexport { minSupportedObsidianVersion } from \"./service.js\";\n\nexport type { ObsidianServiceOptions, ObsidianCapabilityOptions } from \"./types.js\";\nexport type { ObsidianBrowserCommands, ExecuteObsidianArg } from \"./browserCommands.js\";\nexport { default as obsidianPage } from \"./pageobjects/obsidianPage.js\";\nexport type { ObsidianPage } from \"./pageobjects/obsidianPage.js\";\n\nexport type { PluginEntry, DownloadedPluginEntry, ThemeEntry, DownloadedThemeEntry } from \"obsidian-launcher\";\n\n// Some convenience helpers for use in wdio.conf.ts\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 * @param cacheDir Obsidian cache dir, defaults to `.obsidian-cache`.\n */\nexport async function obsidianBetaAvailable(cacheDir?: string) {\n const launcher = new ObsidianLauncher({cacheDir: cacheDir});\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 * @param appVersion Obsidian version string or \"latest\", \"latest-beta\" or \"earliest\". \"earliest\" will use the \n * minAppVersion set in your manifest.json.\n * @param installerVersion Obsidian version string or \"latest\" or \"earliest\". \"earliest\" will use the oldest\n * installer version compatible with the appVersion.\n * @param cacheDir Obsidian cache dir, defaults to `.obsidian-cache`.\n * @returns [appVersion, installerVersion] with any \"latest\" etc. resolved to specific versions.\n */\nexport async function resolveObsidianVersions(\n appVersion: string, installerVersion: string, cacheDir?: string,\n): Promise<[string, string]> {\n const launcher = new ObsidianLauncher({cacheDir: cacheDir});\n return await launcher.resolveVersions(appVersion, installerVersion);\n\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, { DownloadedPluginEntry, DownloadedThemeEntry } from \"obsidian-launcher\"\nimport browserCommands from \"./browserCommands.js\"\nimport { ObsidianCapabilityOptions, ObsidianServiceOptions, OBSIDIAN_CAPABILITY_KEY } from \"./types.js\"\nimport obsidianPage from \"./pageobjects/obsidianPage.js\"\nimport { sleep } from \"./utils.js\"\nimport semver from \"semver\"\nimport _ from \"lodash\"\n\n\nconst log = logger(\"wdio-obsidian-service\");\n\nfunction getDefaultCacheDir() {\n return path.resolve(process.env.WEBDRIVER_CACHE_DIR ?? process.env.OBSIDIAN_CACHE ?? \"./.obsidian-cache\")\n}\n\n/**\n * Minimum Obsidian version that wdio-obsidian-service supports.\n */\nexport const minSupportedObsidianVersion: string = \"1.0.3\"\n\n\n/**\n * wdio launcher service.\n * Use in wdio.conf.ts 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\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(),\n versionsUrl: options.versionsUrl,\n communityPluginsUrl: options.communityPluginsUrl, communityThemesUrl: options.communityThemesUrl,\n });\n this.helperPluginPath = path.resolve(path.join(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 if (!Array.isArray(capabilities)) {\n capabilities = Object.values(capabilities as Capabilities.RequestedMultiremoteCapabilities).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 try {\n for (const cap of obsidianCapabilities) {\n const obsidianOptions = cap[OBSIDIAN_CAPABILITY_KEY] ?? {};\n \n const vault = obsidianOptions.vault != undefined ? path.resolve(obsidianOptions.vault) : undefined;\n \n const [appVersion, installerVersion] = await this.obsidianLauncher.resolveVersions(\n cap.browserVersion ?? cap[OBSIDIAN_CAPABILITY_KEY]?.appVersion ?? \"latest\",\n obsidianOptions.installerVersion ?? \"earliest\",\n );\n const installerVersionInfo = await this.obsidianLauncher.getVersionInfo(installerVersion);\n if (semver.lt(appVersion, minSupportedObsidianVersion)) {\n throw Error(`Minimum supported Obsidian version is ${minSupportedObsidianVersion}`)\n }\n\n let installerPath = obsidianOptions.binaryPath;\n if (!installerPath) {\n installerPath = await this.obsidianLauncher.downloadInstaller(installerVersion);\n }\n let appPath = obsidianOptions.appPath;\n if (!appPath) {\n appPath = await this.obsidianLauncher.downloadApp(appVersion);\n }\n let chromedriverPath = cap['wdio:chromedriverOptions']?.binary\n // wdio can download chromedriver for versions greater than 115 automatically\n if (!chromedriverPath && Number(installerVersionInfo.chromeVersion!.split(\".\")[0]) <= 115) {\n chromedriverPath = await this.obsidianLauncher.downloadChromedriver(installerVersion);\n }\n\n let plugins = obsidianOptions.plugins ?? [\".\"];\n plugins.push(this.helperPluginPath); // Always install the helper plugin\n plugins = await this.obsidianLauncher.downloadPlugins(plugins);\n\n const themes = await this.obsidianLauncher.downloadThemes(obsidianOptions.themes ?? []);\n\n if (obsidianOptions.vault != undefined && !fs.existsSync(obsidianOptions.vault)) {\n throw Error(`Vault \"${obsidianOptions.vault}\" doesn't exist`)\n }\n\n const args = [\n // Workaround for SUID issue on AppImages. See https://github.com/electron/electron/issues/42510\n ...(process.platform == 'linux' ? [\"--no-sandbox\"] : []),\n ...(cap['goog:chromeOptions']?.args ?? [])\n ];\n\n cap.browserName = \"chrome\";\n cap.browserVersion = installerVersionInfo.chromeVersion;\n cap[OBSIDIAN_CAPABILITY_KEY] = {\n ...obsidianOptions,\n plugins: plugins,\n themes: themes,\n binaryPath: installerPath,\n appPath: appPath,\n vault: vault,\n appVersion: appVersion, // Resolve the versions\n installerVersion: installerVersion,\n };\n cap['goog:chromeOptions'] = {\n binary: installerPath,\n windowTypes: [\"app\", \"webview\"],\n ...cap['goog:chromeOptions'],\n args: args,\n }\n cap['wdio:chromedriverOptions'] = {\n // allowedIps is not included in the types, but gets passed as --allowed-ips to chromedriver.\n // It defaults to [\"0.0.0.0\"] which makes Windows Firewall complain, and we don't need remote\n // connections anyways.\n allowedIps: [],\n ...cap['wdio:chromedriverOptions'],\n binary: chromedriverPath,\n } as any\n cap[\"wdio:enforceWebDriverClassic\"] = true;\n }\n } catch (e: any) {\n // By default wdio just logs service errors, throwing this makes it bail if anything goes wrong.\n throw new SevereServiceError(`Failed to download and setup Obsidian. Caused by: ${e.stack}\\n`+\n ` ------The above causes:-----`);\n }\n }\n}\n\n\n/**\n * wdio worker service.\n * Use in wdio.conf.ts like so:\n * ```ts\n * services: ['obsidian'],\n * ```\n * @hidden\n */\nexport class ObsidianWorkerService implements Services.ServiceInstance {\n private obsidianLauncher: ObsidianLauncher\n /** Directories to clean up after the tests */\n private tmpDirs: string[]\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(),\n versionsUrl: options.versionsUrl,\n communityPluginsUrl: options.communityPluginsUrl, communityThemesUrl: options.communityThemesUrl,\n });\n this.tmpDirs = [];\n }\n\n /**\n * Setup vault and config dir for a sandboxed Obsidian instance.\n */\n private async setupObsidian(obsidianOptions: ObsidianCapabilityOptions) {\n let vault = obsidianOptions.vault;\n if (vault != undefined) {\n log.info(`Opening vault ${obsidianOptions.vault}`);\n vault = await this.obsidianLauncher.setupVault({\n vault,\n copy: true,\n plugins: obsidianOptions.plugins,\n themes: obsidianOptions.themes,\n });\n this.tmpDirs.push(vault);\n } else {\n log.info(`Opening Obsidian without a vault`)\n }\n\n const configDir = await this.obsidianLauncher.setupConfigDir({\n appVersion: obsidianOptions.appVersion!, installerVersion: obsidianOptions.installerVersion!,\n appPath: obsidianOptions.appPath!,\n vault: vault,\n });\n this.tmpDirs.push(configDir);\n\n return configDir;\n }\n\n /**\n * Wait for Obsidian to fully boot.\n */\n private async waitForReady(browser: WebdriverIO.Browser) {\n if (browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].vault != undefined) {\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 }\n }\n\n /**\n * Handles vault and sandboxed config directory setup.\n */\n async beforeSession(config: Options.Testrunner, capabilities: WebdriverIO.Capabilities) {\n if (!capabilities[OBSIDIAN_CAPABILITY_KEY]) return;\n\n const configDir = await this.setupObsidian(capabilities[OBSIDIAN_CAPABILITY_KEY]);\n\n capabilities['goog:chromeOptions']!.args = [\n `--user-data-dir=${configDir}`,\n ...(capabilities['goog:chromeOptions']!.args ?? [])\n ];\n }\n\n /**\n * Returns a plugin list with only the selected plugin ids enabled.\n */\n private selectPlugins(currentPlugins: DownloadedPluginEntry[], selection?: string[]) {\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 /**\n * Returns a theme list with only the selected theme enabled.\n */\n private selectThemes(currentThemes: DownloadedThemeEntry[], selection?: string) {\n if (selection !== undefined) {\n if (selection != \"default\" && currentThemes.every((t: any) => t.name != selection)) {\n throw Error(`Unknown theme: ${selection}`);\n }\n return currentThemes.map((t: any) => ({...t, enabled: selection != 'default' && t.name === selection}));\n } else {\n return currentThemes;\n }\n }\n\n /**\n * Setup custom browser commands.\n */\n async before(capabilities: WebdriverIO.Capabilities, specs: never, browser: WebdriverIO.Browser) {\n // There's a slow event listener link on the browser \"command\" event when you reloadSession that causes some\n // warnings. This will silence them. TODO: Make issue or PR to wdio to fix this.\n browser.setMaxListeners(1000);\n\n if (!capabilities[OBSIDIAN_CAPABILITY_KEY]) return;\n\n const service = this; // eslint-disable-line @typescript-eslint/no-this-alias\n const reloadObsidian: typeof browser['reloadObsidian'] = async function(\n this: WebdriverIO.Browser,\n {vault, plugins, theme} = {},\n ) {\n const oldObsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];\n let newCapabilities: WebdriverIO.Capabilities\n\n if (vault) {\n const newObsidianOptions = {\n ...oldObsidianOptions,\n vault: path.resolve(vault),\n plugins: service.selectPlugins(oldObsidianOptions.plugins, plugins),\n themes: service.selectThemes(oldObsidianOptions.themes, theme),\n }\n \n const configDir = await service.setupObsidian(newObsidianOptions);\n \n const newArgs = [\n `--user-data-dir=${configDir}`,\n ...this.requestedCapabilities['goog:chromeOptions'].args.filter((arg: string) => {\n const match = arg.match(/^--user-data-dir=(.*)$/);\n return !match || !service.tmpDirs.includes(match[1]);\n }),\n ]\n \n newCapabilities = {\n [OBSIDIAN_CAPABILITY_KEY]: newObsidianOptions,\n 'goog:chromeOptions': {\n ...this.requestedCapabilities['goog:chromeOptions'],\n args: newArgs,\n },\n };\n } else {\n // preserve vault and config dir\n newCapabilities = {};\n // Since we aren't recreating the vault, we'll need to reset plugins and themes here if specified.\n if (plugins) {\n const enabledPlugins = await browser.executeObsidian(({app}) =>\n [...(app as any).plugins.enabledPlugins].sort()\n )\n for (const pluginId of _.difference(enabledPlugins, plugins, ['wdio-obsidian-service-plugin'])) {\n await obsidianPage.disablePlugin(pluginId);\n }\n for (const pluginId of _.difference(plugins, enabledPlugins)) {\n await obsidianPage.enablePlugin(pluginId);\n }\n }\n if (theme) {\n await obsidianPage.setTheme(theme);\n }\n // Obsidian debounces saves to the config dir, and so changes to configuration made in the tests may not\n // get saved to disk before the reboot. Here I manually trigger save for plugins and themes, but other\n // configurations might not get saved. I haven't found a better way to flush everything than just\n // waiting a bit. Wdio has ways to mock the clock, which might work, but it's only supported when using\n // BiDi, which I can't get working on Obsidian.\n await browser.executeObsidian(async ({app}) => await Promise.all([\n (app as any).plugins.saveConfig(),\n (app.vault as any).saveConfig(),\n ]))\n await sleep(2000);\n }\n\n const sessionId = await browser.reloadSession({\n // if browserName is set, reloadSession tries to restart the driver entirely, so unset those\n ..._.omit(this.requestedCapabilities, ['browserName', 'browserVersion']),\n ...newCapabilities,\n });\n await service.waitForReady(this);\n return sessionId;\n }\n\n await browser.addCommand(\"reloadObsidian\", reloadObsidian);\n\n for (const [name, cmd] of Object.entries(browserCommands)) {\n await browser.addCommand(name, cmd);\n }\n\n await service.waitForReady(browser);\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 type { ObsidianBrowserCommands } from \"./browserCommands.js\";\nimport type { PluginEntry, ThemeEntry } from \"obsidian-launcher\";\n\nexport const OBSIDIAN_CAPABILITY_KEY = \"wdio:obsidianOptions\" as const;\n\nexport interface ObsidianServiceOptions {\n /**\n * Override the `obsidian-versions.json` used by the service. Can be a file URL.\n * This is only really useful for this package's own internal tests.\n */\n versionsUrl?: string,\n /**\n * Override the `community-plugins.json` used by the service. Can be a file URL.\n * This is only really useful for this package's own internal tests.\n */\n communityPluginsUrl?: string,\n /**\n * Override the `community-css-themes.json` used by the service. Can be a file URL.\n * This is only really useful for this package's own internal tests.\n */\n communityThemesUrl?: string,\n}\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 current latest non-beta Obsidian version\n * - \"latest-beta\": Run the current latest beta Obsidian version (or latest is there is no current beta)\n * - \"earliest\": Run the `minAppVersion` set in set in your `manifest.json`\n * \n * Defaults to \"latest\".\n * \n * To download beta versions you'll need to have an Obsidian account with Catalyst and set the `OBSIDIAN_USERNAME`\n * and `OBSIDIAN_PASSWORD` environment variables. 2FA needs to be disabled.\n * \n * You can also use the wdio capability `browserVersion` field to set the Obsidian version.\n */\n appVersion?: string\n\n /**\n * Version of the Obsidian installer to download and run.\n * \n * Note that Obsidian is distributed in two parts, the \"installer\" which is the executable containing Electron, and\n * the \"app\" which is a bundle of JavaScript containing the Obsidian code. Obsidian's self-update system only\n * updates the JavaScript bundle, and not the base installer/Electron version. This makes Obsidian's auto-update\n * fast as it only needs to download a few MiB of JS instead of all of Electron. But, it means different users with\n * the same Obsidian app version may be running on different versions of Electron, which can cause subtle\n * differences in plugin behavior if you are using newer JavaScript features and the like in your plugin.\n * \n * Can be set to a specific version string or one of:\n * - \"latest\": Run the latest Obsidian installer.\n * - \"earliest\": Run the oldest Obsidian installer compatible with the specified Obsidian app version.\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. You\n * can also pass objects. If you pass an object it can contain one of either `path` (to install a local plugin),\n * `repo` (to install a plugin from github), or `id` (to install a community plugin). You can set `enabled: false`\n * to install the plugin but start it disabled. You can enable the plugin later using `reloadObsidian` or the\n * `enablePlugin` command.\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. You can also pass an object. If you pass an object it can\n * contain one of either `path` (to install a local theme), `repo` (to install a theme from github), or `name` (to\n * install a community theme). You can set `enabled: false` to install the theme, but start it disabled. You can\n * only have one enabled theme, so if you pass multiple you'll have to disable all 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 `browser.reloadObsidian` to open a vault during your tests.\n */\n vault?: string,\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\ndeclare global {\n namespace WebdriverIO {\n interface Capabilities {\n [OBSIDIAN_CAPABILITY_KEY]?: ObsidianCapabilityOptions,\n }\n\n interface Browser extends ObsidianBrowserCommands {}\n }\n}\n","import * as path from \"path\"\nimport * as fsAsync from \"fs/promises\"\nimport { OBSIDIAN_CAPABILITY_KEY } from \"../types.js\";\nimport { TFile } from \"obsidian\";\nimport _ from \"lodash\";\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 */\nclass ObsidianPage {\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 async getVaultPath(): Promise<string|undefined> {\n if (browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].vault == undefined) {\n return undefined; // no vault open\n } else { // return the actual path to the vault\n return await browser.executeObsidian(({app, obsidian}) => {\n if (app.vault.adapter instanceof obsidian.FileSystemAdapter) {\n return app.vault.adapter.getBasePath()\n } else { // TODO handle CapacitorAdapater\n throw new Error(`Unrecognized DataAdapater type`)\n };\n })\n }\n }\n\n /**\n * Enables a plugin by ID\n */\n async enablePlugin(pluginId: string): Promise<void> {\n await browser.executeObsidian(\n async ({app}, pluginId) => await (app as any).plugins.enablePluginAndSave(pluginId),\n pluginId,\n );\n }\n\n /**\n * Disables a plugin by ID\n */\n async disablePlugin(pluginId: string): Promise<void> {\n await browser.executeObsidian(\n async ({app}, pluginId) => await (app as any).plugins.disablePluginAndSave(pluginId),\n pluginId,\n );\n }\n\n /**\n * Sets the theme. Pass \"default\" to reset to the Obsidian theme.\n */\n async setTheme(themeName: string): Promise<void> {\n themeName = themeName == 'default' ? '' : themeName;\n await browser.executeObsidian(\n async ({app}, themeName) => await (app as any).customCss.setTheme(themeName),\n themeName,\n )\n }\n\n /**\n * Opens a file in a new tab.\n */\n async openFile(path: string) {\n await browser.executeObsidian(async ({app, obsidian}, path) => {\n const file = app.vault.getAbstractFileByPath(path);\n if (file instanceof obsidian.TFile) {\n await app.workspace.getLeaf('tab').openFile(file);\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 vaultPath = (await this.getVaultPath())!;\n const workspacesPath = path.join(vaultPath, '.obsidian/workspaces.json');\n const layoutName = layout;\n try {\n const fileContent = await fsAsync.readFile(workspacesPath, 'utf-8');\n layout = JSON.parse(fileContent)?.workspaces?.[layoutName];\n } catch {\n throw new Error(`No workspace ${layoutName} found in .obsidian/workspaces.json`);\n }\n }\n\n // Click on the status-bar to focus the main window in case there are multiple Obsidian windows panes\n await $(\".status-bar\").click();\n await browser.executeObsidian(async ({app}, layout) => {\n await app.workspace.changeLayout(layout)\n }, layout)\n }\n\n /**\n * Resets the vault files to the original state by deleting/creating/modifying vault files in place without\n * reloading Obsidian.\n * \n * This will only reset regular vault files, it won't touch anything under `.obsidian`, and it won't reset any\n * config and app state you've set in Obsidian. But if all you need is to reset the vault files, this can be used as\n * a faster alternative to reloadObsidian.\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 an different vault, and it will sync the current vault to match that one (similar to \"rsync\"). Or,\n * instead of passing a vault path you can pass an object mapping vault file paths to file content. 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 the files will be merged. This can be useful if you want to add a few small\n * 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 resetVaultFiles(...vaults: (string|Record<string, string>)[]) {\n const origVaultPath: string = browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].vault;\n vaults = vaults.length == 0 ? [origVaultPath] : vaults;\n\n const newVaultFiles: Record<string, {content?: string, path?: string}> = {};\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 file of files) {\n const absPath = path.join(file.parentPath, file.name);\n const obsPath = path.relative(vault, absPath).split(path.sep).join(\"/\");\n if (file.isFile() && !obsPath.startsWith(\".obsidian/\")) {\n newVaultFiles[obsPath] = {path: absPath};\n }\n }\n } else {\n for (const [file, content] of Object.entries(vault)) {\n newVaultFiles[file] = {content};\n }\n }\n }\n\n // Get all subfolders in the new vault, sort parents are before children\n const newVaultFolders = _(newVaultFiles)\n .keys()\n .map(p => path.dirname(p))\n .filter(p => p != \".\")\n .sort().uniq()\n .value();\n\n await browser.executeObsidian(async ({app, obsidian}, newVaultFolders, newVaultFiles) => {\n const fs = require('fs');\n\n // \"rsync\" the vault\n for (const newFolder of newVaultFolders) {\n let currFile = app.vault.getAbstractFileByPath(newFolder);\n if (currFile && currFile instanceof obsidian.TFile) {\n await app.vault.delete(currFile);\n currFile = null;\n }\n if (!currFile) {\n await app.vault.createFolder(newFolder);\n }\n }\n // sort reversed, so children are before parents\n const currVaultFolders = app.vault.getAllLoadedFiles()\n .filter(f => f instanceof obsidian.TFolder)\n .sort((a, b) => b.path.localeCompare(a.path));\n const newVaultFoldersSet = new Set(newVaultFolders)\n for (const currFolder of currVaultFolders) {\n if (!newVaultFoldersSet.has(currFolder.path)) {\n await app.vault.delete(currFolder, true);\n }\n }\n\n for (let [newFilePath, newFileSource] of Object.entries(newVaultFiles)) {\n let currFile = app.vault.getAbstractFileByPath(newFilePath);\n const content = newFileSource.content ?? await fs.readFileSync(newFileSource.path!, 'utf-8');\n if (currFile) {\n await app.vault.modify(currFile as TFile, content);\n } else {\n await app.vault.create(newFilePath, content);\n }\n }\n const newVaultFilesSet = new Set(Object.keys(newVaultFiles));\n for (const currVaultFile of app.vault.getFiles()) {\n if (!newVaultFilesSet.has(currVaultFile.path)) {\n await app.vault.delete(currVaultFile);\n }\n }\n }, newVaultFolders, newVaultFiles);\n }\n}\n\n/**\n * Instance of ObsidianPage with helper methods for writing Obsidian tests\n */\nconst obsidianPage = new ObsidianPage()\nexport default obsidianPage;\nexport { ObsidianPage };\n","import { OBSIDIAN_CAPABILITY_KEY } from \"./types.js\";\nimport type * as obsidian from \"obsidian\"\nimport obsidianPage, { ObsidianPage } from \"./pageobjects/obsidianPage.js\"\n\n/**\n * Argument passed to the `executeObsidian` browser command.\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 converted to camelCase\n * for ease of destructuring.\n */\n plugins: Record<string, obsidian.Plugin>,\n}\n\nconst browserCommands = {\n /**\n * Returns the Obsidian app version this test is running under.\n */\n async getObsidianVersion(this: WebdriverIO.Browser): Promise<string> {\n return this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].appVersion;\n },\n\n /**\n * Returns the Obsidian installer version this test is running under.\n */\n async getObsidianInstallerVersion(this: WebdriverIO.Browser): Promise<string> {\n return this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].installerVersion;\n },\n\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 * \n * See also: https://webdriver.io/docs/api/browser/execute\n * \n * Example usage\n * ```ts\n * const file = browser.executeObsidian(({app, obsidian}, path) => {\n * return app.vault.getMarkdownFiles().find(f => f.path == path);\n * })\n * ```\n * \n * Note: The same caveats as `browser.execute` apply. The function will be stringified and then run inside Obsidian,\n * so you can't capture any local variables. E.g.\n * \n * This *won't* work:\n * ```ts\n * import { FileView } from Obsidian\n * browser.executeObsidian(({app, obsidian}) => {\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 func: (obs: ExecuteObsidianArg, ...params: Params) => Return,\n ...params: Params\n ): Promise<Return> {\n return await browser.execute<Return, Params>(\n `return (${func.toString()}).call(null, {...window.wdioObsidianService}, ...arguments)`,\n ...params,\n )\n },\n\n /**\n * Executes an Obsidian command by id.\n * @param id Id of the command to run.\n */\n async executeObsidianCommand(this: WebdriverIO.Browser, id: string) {\n const result = await this.executeObsidian(({app}, id) => (app as any).commands.executeCommandById(id), id);\n if (!result) {\n throw Error(`Obsidian command ${id} not found or failed.`);\n }\n },\n\n /**\n * Returns the Workspace page 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 async getObsidianPage(this: WebdriverIO.Browser): Promise<ObsidianPage> {\n return obsidianPage;\n }\n} as const\n\n/** Define this type separately so we can @inline it in typedoc */\ntype PlainObsidianBrowserCommands = typeof browserCommands;\n\n/**\n * Extra commands added to the WDIO Browser instance.\n * \n * See also: https://webdriver.io/docs/api/browser#custom-commands\n * @interface\n */\nexport type ObsidianBrowserCommands = PlainObsidianBrowserCommands & {\n // This command is implemented in the service hooks.\n /**\n * Relaunch obsidian. Can be used to switch to a new vault, change the plugin list, or just to reboot\n * Obsidian.\n * \n * As this does a full reboot of Obsidian, avoid calling this too often so you don't slow your tests down.\n * You can also set the vault in the `wdio.conf.ts` capabilities section which may be useful if all your\n * tests use the same vault.\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\n * creating a 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.ts 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.ts.\n * @returns Returns the new sessionId (same as browser.reloadSession()).\n */\n reloadObsidian(params?: {\n vault?: string,\n plugins?: string[], theme?: string,\n }): Promise<string>;\n};\nexport default browserCommands;\n","export async function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":["ObsidianLauncher","fsAsync","path","pluginId","themeName","path","layout","newVaultFolders","newVaultFiles","fs","id","resolve","_","path","browser","resolve","fsAsync","launcher","ObsidianLauncher"]}
@@ -1,11 +1,20 @@
1
1
  /** Plugin that is automatically loaded during tests and sets up some global variables. */
2
2
  const obsidian = require('obsidian');
3
3
 
4
+ function toCamelCase(s) {
5
+ return s.replace(/-\w/g, m => m[1].toUpperCase());
6
+ }
7
+
4
8
  class WdioObsidianServicePlugin extends obsidian.Plugin {
5
9
  async onload() {
6
10
  const globals = {
7
11
  app: this.app,
8
12
  obsidian: obsidian,
13
+ plugins: Object.fromEntries(
14
+ Object.entries(this.app.plugins.plugins)
15
+ .filter(([id, plugin]) => id != "wdio-obsidian-service-plugin")
16
+ .map(([id, plugin]) => [toCamelCase(id), plugin])
17
+ ),
9
18
  }
10
19
 
11
20
  window.wdioObsidianService = globals;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "wdio-obsidian-service",
3
- "version": "0.3.0",
4
- "description": "TODO",
3
+ "version": "0.4.0",
4
+ "description": "A WebdriverIO service for end-to-end testing of Obsidian plugins",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -27,29 +27,29 @@
27
27
  "bugs": {
28
28
  "url": "https://github.com/jesse-r-s-hines/wdio-obsidian-service/issues"
29
29
  },
30
- "homepage": "https://github.com/jesse-r-s-hines/wdio-obsidian-service#readme",
30
+ "homepage": "https://jesse-r-s-hines.github.io/wdio-obsidian-service/modules/wdio-obsidian-service.html",
31
31
  "devDependencies": {
32
32
  "@types/chai": "^5.0.1",
33
33
  "@types/lodash": "^4.17.14",
34
34
  "@types/mocha": "^10.0.10",
35
35
  "@types/node": "^18.11.9",
36
- "@wdio/cli": "^9.5.7",
37
- "@wdio/local-runner": "^9.5.7",
36
+ "@wdio/cli": "^9.5.0",
37
+ "@wdio/local-runner": "^9.5.0",
38
38
  "@wdio/mocha-framework": "^9.5.0",
39
39
  "@wdio/spec-reporter": "^9.5.0",
40
40
  "@wdio/types": "^9.5.0",
41
41
  "chai": "^5.1.2",
42
- "mocha": "^11.0.1",
42
+ "mocha": "^10.3.0",
43
43
  "npm-run-all": "^4.1.5",
44
44
  "ts-node": "^10.9.2",
45
45
  "tsup": "^8.3.5",
46
46
  "tsx": "^4.19.2",
47
47
  "typescript": "^5.8.2",
48
- "wdio-obsidian-reporter": "^0.3.0"
48
+ "wdio-obsidian-reporter": "^0.4.0"
49
49
  },
50
50
  "dependencies": {
51
51
  "lodash": "^4.17.21",
52
- "obsidian-launcher": "^0.3.0",
52
+ "obsidian-launcher": "^0.4.0",
53
53
  "semver": "^7.7.1"
54
54
  },
55
55
  "peerDependencies": {