wdio-obsidian-service 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -14
- package/dist/index.d.ts +12 -5
- package/dist/index.js +96 -51
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -2,18 +2,21 @@
|
|
|
2
2
|
# WDIO Obsidian Service
|
|
3
3
|
|
|
4
4
|
`wdio-obsidian-service` lets you test [Obsidian](https://obsidian.md) plugins end-to-end using
|
|
5
|
-
[WebdriverIO](https://webdriver.io). The service
|
|
5
|
+
[WebdriverIO](https://webdriver.io). The service handles:
|
|
6
6
|
- Downloading and installing Obsidian
|
|
7
7
|
- Testing your plugin on different Obsidian app versions and installer/electron versions
|
|
8
8
|
- Opening and switching between test vaults during your tests
|
|
9
9
|
- Downloading Chromedriver matching the Obsidian electron version
|
|
10
|
-
- Sandboxing Obsidian so tests don't interfere with your system Obsidian installation
|
|
10
|
+
- Sandboxing Obsidian so tests don't interfere with your system Obsidian installation or each other
|
|
11
|
+
- Running tests in parallel
|
|
11
12
|
- Provides helper functions for common testing tasks
|
|
12
13
|
|
|
13
14
|
## Installation and Setup
|
|
14
|
-
If you want to get going quickly, you can
|
|
15
|
+
If you want to get going quickly, you can use the
|
|
15
16
|
[wdio-obsidian-service sample plugin](https://github.com/jesse-r-s-hines/wdio-obsidian-service-sample-plugin) which has
|
|
16
|
-
all the setup you need to build and
|
|
17
|
+
all the setup you need to build and end-to-end test Obsidian plugins, including GitHub CI workflows.
|
|
18
|
+
|
|
19
|
+
See also: [WebdriverIO | Getting Started](https://webdriver.io/docs/gettingstarted).
|
|
17
20
|
|
|
18
21
|
To setup wdio-obsidian-service manually, run the WebdriverIO Starter Toolkit:
|
|
19
22
|
```bash
|
|
@@ -41,7 +44,7 @@ And add this `tsconfig.json`:
|
|
|
41
44
|
}
|
|
42
45
|
```
|
|
43
46
|
|
|
44
|
-
Setup `wdio.conf.ts`
|
|
47
|
+
Setup your `wdio.conf.ts` like so:
|
|
45
48
|
```ts
|
|
46
49
|
import * as path from "path"
|
|
47
50
|
|
|
@@ -57,10 +60,16 @@ export const config: WebdriverIO.Config = {
|
|
|
57
60
|
|
|
58
61
|
capabilities: [{
|
|
59
62
|
browserName: 'obsidian',
|
|
63
|
+
// obsidian app version to download
|
|
60
64
|
browserVersion: "latest",
|
|
61
65
|
'wdio:obsidianOptions': {
|
|
66
|
+
// obsidian installer version
|
|
67
|
+
// (see "Obsidian App vs Installer Versions" below)
|
|
62
68
|
installerVersion: "earliest",
|
|
63
69
|
plugins: ["."],
|
|
70
|
+
// If you need to switch between multiple vaults, you can omit
|
|
71
|
+
// this and use reloadObsidian to open vaults during the tests
|
|
72
|
+
vault: "test/vaults/simple",
|
|
64
73
|
},
|
|
65
74
|
}],
|
|
66
75
|
|
|
@@ -106,10 +115,40 @@ describe('Test my plugin', function() {
|
|
|
106
115
|
})
|
|
107
116
|
```
|
|
108
117
|
|
|
109
|
-
|
|
110
|
-
|
|
118
|
+
`wdio-obsidian-service` has a few helper functions that can be useful in your wdio conf, such as `obsidianBetaAvailable`
|
|
119
|
+
which checks if there's a current Obsidian beta and you have the credentials to download it. E.g. to test your
|
|
120
|
+
`minAppVersion`, `latest`, and `latest-beta` if it's available use:
|
|
121
|
+
```ts
|
|
122
|
+
import { obsidianBetaAvailable } from "wdio-obsidian-service";
|
|
123
|
+
const cacheDir = path.resolve(".obsidian-cache");
|
|
124
|
+
|
|
125
|
+
const versions: [string, string][] = [
|
|
126
|
+
["earliest", "earliest"],
|
|
127
|
+
["latest", "latest"],
|
|
128
|
+
];
|
|
129
|
+
if (await obsidianBetaAvailable(cacheDir)) {
|
|
130
|
+
versions.push(["latest-beta", "latest"]);
|
|
131
|
+
}
|
|
111
132
|
|
|
112
|
-
|
|
133
|
+
export const config: WebdriverIO.Config = {
|
|
134
|
+
cacheDir: cacheDir,
|
|
135
|
+
|
|
136
|
+
capabilities: versions.map(([appVersion, installerVersion]) => ({
|
|
137
|
+
browserName: 'obsidian',
|
|
138
|
+
browserVersion: appVersion,
|
|
139
|
+
'wdio:obsidianOptions': {
|
|
140
|
+
installerVersion: installerVersion,
|
|
141
|
+
plugins: ["."],
|
|
142
|
+
},
|
|
143
|
+
})),
|
|
144
|
+
|
|
145
|
+
// ...
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
Note, to use top-level await you'll need to rename `wdio.conf.ts` to `wdio.conf.mts` so it's loaded as an ESM module.
|
|
149
|
+
|
|
150
|
+
You can see the [sample plugin](https://github.com/jesse-r-s-hines/wdio-obsidian-service-sample-plugin) for more
|
|
151
|
+
examples of how to write your wdio conf and e2e tests.
|
|
113
152
|
|
|
114
153
|
### Platform Support
|
|
115
154
|
`wdio-obsidian-service` works on Windows, Linux, and MacOS.
|
|
@@ -131,7 +170,7 @@ different versions of Electron, which can cause subtle differences in plugin beh
|
|
|
131
170
|
|
|
132
171
|
You can check your current Obsidian app and installer versions in the General settings tab.
|
|
133
172
|
|
|
134
|
-
You can specify both `appVersion` and `installerVersion` in your `wdio.conf.
|
|
173
|
+
You can specify both `appVersion` and `installerVersion` in your `wdio.conf.mts` capabilities section.
|
|
135
174
|
|
|
136
175
|
To set the app version use `browserVersion` or `'wdio:obsidianOptions'.appVersion`. It can be set to one of:
|
|
137
176
|
- a specific version string like "1.7.7"
|
|
@@ -147,7 +186,7 @@ To set the installer version use `'wdio:obsidianOptions'.installerVersion`. It c
|
|
|
147
186
|
- "earliest": run the oldest Obsidian installer compatible with `appVersion`
|
|
148
187
|
|
|
149
188
|
You can see more configuration options for the capabilities
|
|
150
|
-
[here](https://jesse-r-s-hines.github.io/wdio-obsidian-service/
|
|
189
|
+
[here](https://jesse-r-s-hines.github.io/wdio-obsidian-service/wdio-obsidian-service/ObsidianCapabilityOptions.html)
|
|
151
190
|
|
|
152
191
|
### Opening and Switching between Vaults
|
|
153
192
|
If all your tests use the same vault, you can set the vault in the `wdio:obsidianOptions` capabilities section. If you
|
|
@@ -180,13 +219,13 @@ it("test the thing", async function() {
|
|
|
180
219
|
|
|
181
220
|
### API Docs
|
|
182
221
|
API docs, including all configuration options and helper functions, are available
|
|
183
|
-
[here](https://jesse-r-s-hines.github.io/wdio-obsidian-service/
|
|
222
|
+
[here](https://jesse-r-s-hines.github.io/wdio-obsidian-service/wdio-obsidian-service/README.html).
|
|
184
223
|
|
|
185
224
|
### GitHub CI Workflows
|
|
186
|
-
The sample plugin has workflows
|
|
187
|
-
[here](https://github.com/jesse-r-s-hines/wdio-obsidian-service-sample-plugin
|
|
225
|
+
The sample plugin has workflows setup to release and test your plugin, which you can see
|
|
226
|
+
[here](https://github.com/jesse-r-s-hines/wdio-obsidian-service-sample-plugin#github-workflows).
|
|
188
227
|
|
|
189
228
|
### obsidian-launcher CLI
|
|
190
229
|
`wdio-obsidian-service` depends on `obsidian-launcher` so the `obsidian-launcher` CLI is also available, with some
|
|
191
230
|
commands for launching different Obsidian versions. CLI docs available
|
|
192
|
-
[here](https://jesse-r-s-hines.github.io/wdio-obsidian-service/
|
|
231
|
+
[here](https://jesse-r-s-hines.github.io/wdio-obsidian-service/obsidian-launcher/README.html#cli).
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export { DownloadedPluginEntry, DownloadedThemeEntry, PluginEntry, ThemeEntry }
|
|
|
15
15
|
* ```ts
|
|
16
16
|
* import { obsidianPage } from "wdio-obsidian-service";
|
|
17
17
|
* ```
|
|
18
|
+
*
|
|
19
|
+
* @hideconstructor
|
|
18
20
|
*/
|
|
19
21
|
declare class ObsidianPage {
|
|
20
22
|
/**
|
|
@@ -23,6 +25,10 @@ declare class ObsidianPage {
|
|
|
23
25
|
* wdio-obsidian-service copies your vault before running tests, so this is the path to the temporary copy.
|
|
24
26
|
*/
|
|
25
27
|
getVaultPath(): Promise<string | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* Return the path to the Obsidian config dir (".obsidian" unless you've changed it explicitly)
|
|
30
|
+
*/
|
|
31
|
+
getConfigDir(): Promise<string>;
|
|
26
32
|
/**
|
|
27
33
|
* Enables a plugin by ID
|
|
28
34
|
*/
|
|
@@ -107,6 +113,7 @@ interface ExecuteObsidianArg {
|
|
|
107
113
|
* ```
|
|
108
114
|
*/
|
|
109
115
|
plugins: InstalledPlugins;
|
|
116
|
+
require: NodeJS.Require;
|
|
110
117
|
}
|
|
111
118
|
declare const browserCommands: {
|
|
112
119
|
/**
|
|
@@ -123,8 +130,8 @@ declare const browserCommands: {
|
|
|
123
130
|
* - app: Obsidian app instance
|
|
124
131
|
* - obsidian: Full Obsidian API
|
|
125
132
|
* - plugins: Object of all installed plugins, mapped by plugin id converted to camelCase.
|
|
126
|
-
*
|
|
127
|
-
*
|
|
133
|
+
* - require: The customized require function Obsidian makes available to plugins. This is also made available
|
|
134
|
+
* globally, so you can just use `require` directly instead of from ExecuteObsidianArg if you prefer.
|
|
128
135
|
*
|
|
129
136
|
* Like `brower.execute`, you can pass other extra arguments to the function.
|
|
130
137
|
*
|
|
@@ -187,7 +194,7 @@ type ObsidianBrowserCommands = PlainObsidianBrowserCommands & {
|
|
|
187
194
|
* Obsidian.
|
|
188
195
|
*
|
|
189
196
|
* As this does a full reboot of Obsidian, avoid calling this too often so you don't slow your tests down.
|
|
190
|
-
* You can also set the vault in the `wdio.conf.ts` capabilities section which may be useful if all your
|
|
197
|
+
* You can also set the vault in the `wdio.conf.(m)ts` capabilities section which may be useful if all your
|
|
191
198
|
* tests use the same vault.
|
|
192
199
|
*
|
|
193
200
|
* @param params.vault Path to the vault to open. The vault will be copied, so any changes made in your tests won't
|
|
@@ -240,7 +247,7 @@ interface ObsidianCapabilityOptions {
|
|
|
240
247
|
*
|
|
241
248
|
* You can also use the wdio capability `browserVersion` field to set the Obsidian version.
|
|
242
249
|
*
|
|
243
|
-
* See also:
|
|
250
|
+
* See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)
|
|
244
251
|
*/
|
|
245
252
|
appVersion?: string;
|
|
246
253
|
/**
|
|
@@ -250,7 +257,7 @@ interface ObsidianCapabilityOptions {
|
|
|
250
257
|
* electron. Obsidian's auto update only updates the app, so users on the same Obsidian version can be running
|
|
251
258
|
* different Electron versions. You can use this to test your plugin against different installer/electron versions.
|
|
252
259
|
*
|
|
253
|
-
* See also:
|
|
260
|
+
* See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)
|
|
254
261
|
*
|
|
255
262
|
* Can be set to a specific version string or one of:
|
|
256
263
|
* - "latest": Run the latest Obsidian installer.
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,6 @@ var OBSIDIAN_CAPABILITY_KEY = "wdio:obsidianOptions";
|
|
|
16
16
|
// src/pageobjects/obsidianPage.ts
|
|
17
17
|
import * as path from "path";
|
|
18
18
|
import * as fsAsync from "fs/promises";
|
|
19
|
-
import _ from "lodash";
|
|
20
19
|
var ObsidianPage = class {
|
|
21
20
|
/**
|
|
22
21
|
* Returns the path to the vault opened in Obsidian.
|
|
@@ -37,6 +36,14 @@ var ObsidianPage = class {
|
|
|
37
36
|
});
|
|
38
37
|
}
|
|
39
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Return the path to the Obsidian config dir (".obsidian" unless you've changed it explicitly)
|
|
41
|
+
*/
|
|
42
|
+
async getConfigDir() {
|
|
43
|
+
return await browser.executeObsidian(({ app }) => {
|
|
44
|
+
return app.vault.configDir;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
40
47
|
/**
|
|
41
48
|
* Enables a plugin by ID
|
|
42
49
|
*/
|
|
@@ -85,13 +92,14 @@ var ObsidianPage = class {
|
|
|
85
92
|
async loadWorkspaceLayout(layout) {
|
|
86
93
|
if (typeof layout == "string") {
|
|
87
94
|
const vaultPath = await this.getVaultPath();
|
|
88
|
-
const
|
|
95
|
+
const configDir = await this.getConfigDir();
|
|
96
|
+
const workspacesPath = path.join(vaultPath, configDir, "workspaces.json");
|
|
89
97
|
const layoutName = layout;
|
|
90
98
|
try {
|
|
91
99
|
const fileContent = await fsAsync.readFile(workspacesPath, "utf-8");
|
|
92
100
|
layout = JSON.parse(fileContent)?.workspaces?.[layoutName];
|
|
93
101
|
} catch {
|
|
94
|
-
throw new Error(`No workspace ${layoutName} found in
|
|
102
|
+
throw new Error(`No workspace ${layoutName} found in ${configDir}/workspaces.json`);
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
105
|
await browser.executeObsidian(async ({ app }, layout2) => {
|
|
@@ -126,60 +134,97 @@ var ObsidianPage = class {
|
|
|
126
134
|
async resetVault(...vaults) {
|
|
127
135
|
const origVaultPath = browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].vault;
|
|
128
136
|
vaults = vaults.length == 0 ? [origVaultPath] : vaults;
|
|
129
|
-
const
|
|
137
|
+
const configDir = await this.getConfigDir();
|
|
138
|
+
async function readVaultFiles(vault) {
|
|
139
|
+
const files = await fsAsync.readdir(vault, { recursive: true, withFileTypes: true });
|
|
140
|
+
const paths = files.filter((f) => f.isFile()).map((f) => path.relative(vault, path.join(f.parentPath, f.name)).split(path.sep).join("/")).filter((f) => !f.startsWith(configDir + "/"));
|
|
141
|
+
const promises = paths.map(async (p) => [p, await fsAsync.stat(path.join(vault, p))]);
|
|
142
|
+
return new Map(await Promise.all(promises));
|
|
143
|
+
}
|
|
144
|
+
function getFolders(files) {
|
|
145
|
+
return new Set([...files].map((p) => path.dirname(p)).filter((p) => p != "."));
|
|
146
|
+
}
|
|
147
|
+
const newFiles = /* @__PURE__ */ new Map();
|
|
130
148
|
for (let vault of vaults) {
|
|
131
149
|
if (typeof vault == "string") {
|
|
132
150
|
vault = path.resolve(vault);
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
const absPath = path.join(file.parentPath, file.name);
|
|
136
|
-
const obsPath = path.relative(vault, absPath).split(path.sep).join("/");
|
|
137
|
-
if (file.isFile() && !obsPath.startsWith(".obsidian/")) {
|
|
138
|
-
newVaultFiles[obsPath] = { path: absPath };
|
|
139
|
-
}
|
|
151
|
+
for (const [file, stat2] of await readVaultFiles(vault)) {
|
|
152
|
+
newFiles.set(file, { stat: stat2, sourcePath: path.join(vault, file) });
|
|
140
153
|
}
|
|
141
154
|
} else {
|
|
142
|
-
for (const [file,
|
|
143
|
-
|
|
155
|
+
for (const [file, sourceContent] of Object.entries(vault)) {
|
|
156
|
+
newFiles.set(file, { sourceContent });
|
|
144
157
|
}
|
|
145
158
|
}
|
|
146
159
|
}
|
|
147
|
-
const
|
|
148
|
-
await
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
currFile = null;
|
|
155
|
-
}
|
|
156
|
-
if (!currFile) {
|
|
157
|
-
await app.vault.createFolder(newFolder);
|
|
158
|
-
}
|
|
160
|
+
const newFolders = getFolders(newFiles.keys());
|
|
161
|
+
const currFiles = await readVaultFiles(await obsidianPage.getVaultPath());
|
|
162
|
+
const currFolders = getFolders(currFiles.keys());
|
|
163
|
+
const instructions = [];
|
|
164
|
+
for (const currFile of currFiles.keys()) {
|
|
165
|
+
if (!newFiles.has(currFile)) {
|
|
166
|
+
instructions.push({ action: "delete-file", path: currFile });
|
|
159
167
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
await app.vault.delete(currFolder, true);
|
|
165
|
-
}
|
|
168
|
+
}
|
|
169
|
+
for (const currFolder of [...currFolders].sort().reverse()) {
|
|
170
|
+
if (!newFolders.has(currFolder)) {
|
|
171
|
+
instructions.push({ action: "delete-folder", path: currFolder });
|
|
166
172
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
}
|
|
174
|
+
for (const newFolder of [...newFolders].sort()) {
|
|
175
|
+
if (!currFolders.has(newFolder)) {
|
|
176
|
+
instructions.push({ action: "create-folder", path: newFolder });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
for (let [newFile, newFileInfo] of newFiles.entries()) {
|
|
180
|
+
const { stat: newStat, sourcePath, sourceContent } = newFileInfo;
|
|
181
|
+
const args = { path: newFile, sourcePath, sourceContent };
|
|
182
|
+
const currStat = currFiles.get(newFile);
|
|
183
|
+
if (!currStat) {
|
|
184
|
+
instructions.push({ action: "create-file", ...args });
|
|
185
|
+
} else if (
|
|
186
|
+
// check if file has changed (setupVault preserves mtimes)
|
|
187
|
+
!newStat || currStat.mtime.getTime() != newStat.mtime.getTime() || currStat.size != newStat.size
|
|
188
|
+
) {
|
|
189
|
+
instructions.push({ action: "modify-file", ...args });
|
|
175
190
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
191
|
+
}
|
|
192
|
+
await browser.executeObsidian(async ({ app, require: require2 }, instructions2) => {
|
|
193
|
+
const fs2 = require2("fs");
|
|
194
|
+
for (const { action, path: path3, sourcePath, sourceContent } of instructions2) {
|
|
195
|
+
const isHidden = path3.split("/").some((p) => p.startsWith("."));
|
|
196
|
+
if (action == "delete-file") {
|
|
197
|
+
if (isHidden) {
|
|
198
|
+
await app.vault.adapter.remove(path3);
|
|
199
|
+
} else {
|
|
200
|
+
await app.vault.delete(app.vault.getAbstractFileByPath(path3));
|
|
201
|
+
}
|
|
202
|
+
} else if (action == "delete-folder") {
|
|
203
|
+
if (isHidden) {
|
|
204
|
+
await app.vault.adapter.rmdir(path3, true);
|
|
205
|
+
} else {
|
|
206
|
+
await app.vault.delete(app.vault.getAbstractFileByPath(path3), true);
|
|
207
|
+
}
|
|
208
|
+
} else if (action == "create-folder") {
|
|
209
|
+
if (isHidden) {
|
|
210
|
+
await app.vault.adapter.mkdir(path3);
|
|
211
|
+
} else {
|
|
212
|
+
await app.vault.createFolder(path3);
|
|
213
|
+
}
|
|
214
|
+
} else if (action == "create-file" || action == "modify-file") {
|
|
215
|
+
const content = sourceContent ?? await fs2.readFileSync(sourcePath, "utf-8");
|
|
216
|
+
if (isHidden) {
|
|
217
|
+
await app.vault.adapter.write(path3, content);
|
|
218
|
+
} else if (action == "modify-file") {
|
|
219
|
+
await app.vault.modify(app.vault.getAbstractFileByPath(path3), content);
|
|
220
|
+
} else {
|
|
221
|
+
await app.vault.create(path3, content);
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
throw Error(`Unknown action ${action}`);
|
|
180
225
|
}
|
|
181
226
|
}
|
|
182
|
-
},
|
|
227
|
+
}, instructions);
|
|
183
228
|
}
|
|
184
229
|
};
|
|
185
230
|
var obsidianPage = new ObsidianPage();
|
|
@@ -205,8 +250,8 @@ var browserCommands = {
|
|
|
205
250
|
* - app: Obsidian app instance
|
|
206
251
|
* - obsidian: Full Obsidian API
|
|
207
252
|
* - plugins: Object of all installed plugins, mapped by plugin id converted to camelCase.
|
|
208
|
-
*
|
|
209
|
-
*
|
|
253
|
+
* - require: The customized require function Obsidian makes available to plugins. This is also made available
|
|
254
|
+
* globally, so you can just use `require` directly instead of from ExecuteObsidianArg if you prefer.
|
|
210
255
|
*
|
|
211
256
|
* Like `brower.execute`, you can pass other extra arguments to the function.
|
|
212
257
|
*
|
|
@@ -279,7 +324,7 @@ async function sleep(ms) {
|
|
|
279
324
|
|
|
280
325
|
// src/service.ts
|
|
281
326
|
import semver from "semver";
|
|
282
|
-
import
|
|
327
|
+
import _ from "lodash";
|
|
283
328
|
var log = logger("wdio-obsidian-service");
|
|
284
329
|
function getDefaultCacheDir() {
|
|
285
330
|
return path2.resolve(process.env.WEBDRIVER_CACHE_DIR ?? process.env.OBSIDIAN_CACHE ?? "./.obsidian-cache");
|
|
@@ -455,7 +500,7 @@ var ObsidianWorkerService = class {
|
|
|
455
500
|
*/
|
|
456
501
|
selectPlugins(currentPlugins, selection) {
|
|
457
502
|
if (selection !== void 0) {
|
|
458
|
-
const unknownPlugins =
|
|
503
|
+
const unknownPlugins = _.difference(selection, currentPlugins.map((p) => p.id));
|
|
459
504
|
if (unknownPlugins.length > 0) {
|
|
460
505
|
throw Error(`Unknown plugin ids: ${unknownPlugins.join(", ")}`);
|
|
461
506
|
}
|
|
@@ -518,10 +563,10 @@ var ObsidianWorkerService = class {
|
|
|
518
563
|
const enabledPlugins = await browser2.executeObsidian(
|
|
519
564
|
({ app }) => [...app.plugins.enabledPlugins].sort()
|
|
520
565
|
);
|
|
521
|
-
for (const pluginId of
|
|
566
|
+
for (const pluginId of _.difference(enabledPlugins, plugins, ["wdio-obsidian-service-plugin"])) {
|
|
522
567
|
await obsidianPage_default.disablePlugin(pluginId);
|
|
523
568
|
}
|
|
524
|
-
for (const pluginId of
|
|
569
|
+
for (const pluginId of _.difference(plugins, enabledPlugins)) {
|
|
525
570
|
await obsidianPage_default.enablePlugin(pluginId);
|
|
526
571
|
}
|
|
527
572
|
}
|
|
@@ -536,7 +581,7 @@ var ObsidianWorkerService = class {
|
|
|
536
581
|
}
|
|
537
582
|
const sessionId = await browser2.reloadSession({
|
|
538
583
|
// if browserName is set, reloadSession tries to restart the driver entirely, so unset those
|
|
539
|
-
...
|
|
584
|
+
..._.omit(this.requestedCapabilities, ["browserName", "browserVersion"]),
|
|
540
585
|
...newCapabilities
|
|
541
586
|
});
|
|
542
587
|
await service.waitForReady(this);
|
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/** @hidden */\nexport default ObsidianWorkerService;\n/** @hidden */\nexport const launcher = ObsidianLauncherService;\n\nexport type { ObsidianCapabilityOptions, ObsidianServiceOptions } from \"./types.js\";\nexport type { ObsidianBrowserCommands, ExecuteObsidianArg, InstalledPlugins } from \"./browserCommands.js\";\nexport { default as obsidianPage } from \"./pageobjects/obsidianPage.js\";\nexport type { ObsidianPage } from \"./pageobjects/obsidianPage.js\";\nexport type { PluginEntry, DownloadedPluginEntry, ThemeEntry, DownloadedThemeEntry } from \"obsidian-launcher\";\n\nexport { minSupportedObsidianVersion } from \"./service.js\";\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 latest non-beta Obsidian version\n * - \"latest-beta\": Run the latest beta Obsidian version (or latest is there is no current beta)\n * - To download Obsidian beta versions you'll need to have an Obsidian account with Catalyst and set the \n * `OBSIDIAN_USERNAME` and `OBSIDIAN_PASSWORD` environment variables. 2FA needs to be disabled.\n * - \"earliest\": Run the `minAppVersion` set in set in your `manifest.json`\n * \n * Defaults to \"latest\".\n * \n * You can also use the wdio capability `browserVersion` field to set the Obsidian version.\n * \n * See also: \"Obsidian App vs Installer Versions\" {@link wdio-obsidian-service! | README.md}\n */\n appVersion?: string\n\n /**\n * Version of the Obsidian installer to download and run.\n * \n * Obsidian is distributed in two parts, the app which contains the JS, and the installer which is the binary with\n * electron. Obsidian's auto update only updates the app, so users on the same Obsidian version can be running\n * different Electron versions. You can use this to test your plugin against different installer/electron versions.\n * \n * See also: \"Obsidian App vs Installer Versions\" {@link wdio-obsidian-service! | README.md}\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 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 a 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 resetVault(...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 // the require is getting transpiled by tsup, so pull it directly from wdioObsidianService\n const fs = (window as any).wdioObsidianService.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/** Installed plugins, mapped by their id converted to camelCase */\nexport interface InstalledPlugins extends Record<string, obsidian.Plugin> {\n}\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 * You can add types for your plugin(s) here with:\n * ```ts\n * import type MyPlugin from \"../src/main.js\"\n * declare module \"wdio-obsidian-service\" {\n * interface InstalledPlugins {\n * myPlugin: MyPlugin,\n * }\n * }\n * ```\n */\n plugins: InstalledPlugins,\n}\n\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 * You can use `require` inside the function to fetch Obsidian modules, same as you can inside plugins.\n * \n * Like `brower.execute`, you can pass other extra arguments to the function.\n * \n * See also: https://webdriver.io/docs/api/browser/execute\n * \n * Example usage\n * ```ts\n * const file = browser.executeObsidian(({app, obsidian}, path) => {\n * return app.vault.getMarkdownFiles().find(f => f.path == path);\n * })\n * ```\n * \n * Note: The same caveats as `browser.execute` apply. The function will be stringified and then run inside Obsidian,\n * so you can't capture any local variables. E.g.\n * \n * This *won't* work:\n * ```ts\n * import { FileView } from Obsidian\n * browser.executeObsidian(({app, 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 this: WebdriverIO.Browser,\n func: (obs: ExecuteObsidianArg, ...params: Params) => Return,\n ...params: Params\n ): Promise<Return> {\n if (this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].vault == undefined) {\n throw Error(\"No vault open\")\n }\n return await this.execute<Return, Params>(`\n const require = window.wdioObsidianService.require;\n return (${func.toString()}).call(null, {...window.wdioObsidianService}, ...arguments)\n `, ...params)\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
|
+
{"version":3,"sources":["../src/index.ts","../src/service.ts","../src/types.ts","../src/pageobjects/obsidianPage.ts","../src/browserCommands.ts","../src/utils.ts"],"sourcesContent":["/**\n * @module\n * @document ../README.md\n */\nimport ObsidianLauncher from \"obsidian-launcher\";\nimport { ObsidianLauncherService, ObsidianWorkerService } from \"./service.js\";\n/** @hidden */\nexport default ObsidianWorkerService;\n/** @hidden */\nexport const launcher = ObsidianLauncherService;\n\nexport type { ObsidianCapabilityOptions, ObsidianServiceOptions } from \"./types.js\";\nexport type { ObsidianBrowserCommands, ExecuteObsidianArg, InstalledPlugins } from \"./browserCommands.js\";\nexport { default as obsidianPage } from \"./pageobjects/obsidianPage.js\";\nexport type { ObsidianPage } from \"./pageobjects/obsidianPage.js\";\nexport type { PluginEntry, DownloadedPluginEntry, ThemeEntry, DownloadedThemeEntry } from \"obsidian-launcher\";\n\nexport { minSupportedObsidianVersion } from \"./service.js\";\n\n// Some convenience helpers for use in wdio.conf.(m)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 latest non-beta Obsidian version\n * - \"latest-beta\": Run the latest beta Obsidian version (or latest is there is no current beta)\n * - To download Obsidian beta versions you'll need to have an Obsidian account with Catalyst and set the \n * `OBSIDIAN_USERNAME` and `OBSIDIAN_PASSWORD` environment variables. 2FA needs to be disabled.\n * - \"earliest\": Run the `minAppVersion` set in set in your `manifest.json`\n * \n * Defaults to \"latest\".\n * \n * You can also use the wdio capability `browserVersion` field to set the Obsidian version.\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n */\n appVersion?: string\n\n /**\n * Version of the Obsidian installer to download and run.\n * \n * Obsidian is distributed in two parts, the app which contains the JS, and the installer which is the binary with\n * electron. Obsidian's auto update only updates the app, so users on the same Obsidian version can be running\n * different Electron versions. You can use this to test your plugin against different installer/electron versions.\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n *\n * Can be set to a specific version string or one of:\n * - \"latest\": Run the latest Obsidian installer.\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 fs from \"fs\"\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 * \n * @hideconstructor\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 * Return the path to the Obsidian config dir (\".obsidian\" unless you've changed it explicitly)\n */\n async getConfigDir(): Promise<string> {\n return await browser.executeObsidian(({app}) => {\n return app.vault.configDir;\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 configDir = await this.getConfigDir();\n const workspacesPath = path.join(vaultPath, configDir, '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 ${configDir}/workspaces.json`);\n }\n }\n\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 a 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 resetVault(...vaults: (string|Record<string, string>)[]) {\n const origVaultPath: string = browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].vault;\n vaults = vaults.length == 0 ? [origVaultPath] : vaults;\n const configDir = await this.getConfigDir();\n\n async function readVaultFiles(vault: string): Promise<Map<string, fs.Stats>> {\n const files = await fsAsync.readdir(vault, { recursive: true, withFileTypes: true });\n const paths = files\n .filter(f => f.isFile())\n .map(f => path.relative(vault, path.join(f.parentPath, f.name)).split(path.sep).join(\"/\"))\n .filter(f => !f.startsWith(configDir + \"/\"));\n const promises = paths.map(async (p) => [p, await fsAsync.stat(path.join(vault, p))] as const);\n return new Map(await Promise.all(promises));\n }\n\n function getFolders(files: Iterable<string>): Set<string> {\n return new Set([...files].map(p => path.dirname(p)).filter(p => p != '.'));\n }\n\n // merge the vaults\n const newFiles: Map<string, {stat?: fs.Stats, sourcePath?: string, sourceContent?: string}> = new Map();\n for (let vault of vaults) {\n if (typeof vault == \"string\") {\n vault = path.resolve(vault);\n for (const [file, stat] of await readVaultFiles(vault)) {\n newFiles.set(file, {stat, sourcePath: path.join(vault, file)});\n }\n } else {\n for (const [file, sourceContent] of Object.entries(vault)) {\n newFiles.set(file, {sourceContent});\n }\n }\n }\n\n // calculate the changes needed to the current vault\n const newFolders = getFolders(newFiles.keys());\n const currFiles = await readVaultFiles((await obsidianPage.getVaultPath())!);\n const currFolders = getFolders(currFiles.keys());\n\n type FileUpdateInstruction = {\n action: string, path: string,\n sourcePath?: string, sourceContent?: string,\n };\n const instructions: FileUpdateInstruction[] = [];\n\n // delete files\n for (const currFile of currFiles.keys()) {\n if (!newFiles.has(currFile)) {\n instructions.push({action: \"delete-file\", path: currFile});\n }\n }\n // delete folders, sort so children are before parents\n for (const currFolder of [...currFolders].sort().reverse()) {\n if (!newFolders.has(currFolder)) {\n instructions.push({action: \"delete-folder\", path: currFolder});\n }\n }\n // create folders, sort so parents are before children\n for (const newFolder of [...newFolders].sort()) {\n if (!currFolders.has(newFolder)) {\n instructions.push({action: \"create-folder\", path: newFolder});\n }\n }\n // create/modify files\n for (let [newFile, newFileInfo] of newFiles.entries()) {\n const {stat: newStat, sourcePath, sourceContent} = newFileInfo;\n const args = {path: newFile, sourcePath, sourceContent};\n const currStat = currFiles.get(newFile);\n if (!currStat) {\n instructions.push({action: \"create-file\", ...args});\n } else if ( // check if file has changed (setupVault preserves mtimes)\n !newStat ||\n currStat.mtime.getTime() != newStat.mtime.getTime() ||\n currStat.size != newStat.size\n ) {\n instructions.push({action: \"modify-file\", ...args});\n }\n }\n\n await browser.executeObsidian(async ({app, require}, instructions) => {\n // the require is getting transpiled by tsup, so use it from args instead of globally\n const fs = require('fs');\n \n for (const {action, path, sourcePath, sourceContent} of instructions) {\n const isHidden = path.split(\"/\").some(p => p.startsWith(\".\"));\n if (action == \"delete-file\") {\n if (isHidden) {\n await app.vault.adapter.remove(path);\n } else {\n await app.vault.delete(app.vault.getAbstractFileByPath(path)!);\n }\n } else if (action == \"delete-folder\") {\n if (isHidden) {\n await app.vault.adapter.rmdir(path, true);\n } else {\n await app.vault.delete(app.vault.getAbstractFileByPath(path)!, true);\n }\n } else if (action == \"create-folder\") {\n if (isHidden) {\n await app.vault.adapter.mkdir(path);\n } else {\n await app.vault.createFolder(path);\n }\n } else if (action == \"create-file\" || action == \"modify-file\") {\n const content = sourceContent ?? await fs.readFileSync(sourcePath!, 'utf-8');\n if (isHidden) {\n await app.vault.adapter.write(path, content);\n } else if (action == \"modify-file\") {\n await app.vault.modify(app.vault.getAbstractFileByPath(path) as TFile, content);\n } else { // action == \"create-file\"\n await app.vault.create(path, content);\n }\n } else {\n throw Error(`Unknown action ${action}`)\n }\n }\n }, instructions);\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/** Installed plugins, mapped by their id converted to camelCase */\nexport interface InstalledPlugins extends Record<string, obsidian.Plugin> {\n}\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 * You can add types for your plugin(s) here with:\n * ```ts\n * import type MyPlugin from \"../src/main.js\"\n * declare module \"wdio-obsidian-service\" {\n * interface InstalledPlugins {\n * myPlugin: MyPlugin,\n * }\n * }\n * ```\n */\n plugins: InstalledPlugins,\n\n require: NodeJS.Require,\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 * - require: The customized require function Obsidian makes available to plugins. This is also made available\n * globally, so you can just use `require` directly instead of from ExecuteObsidianArg if you prefer.\n * \n * Like `brower.execute`, you can pass other extra arguments to the function.\n * \n * See also: https://webdriver.io/docs/api/browser/execute\n * \n * Example usage\n * ```ts\n * const file = browser.executeObsidian(({app, obsidian}, path) => {\n * return app.vault.getMarkdownFiles().find(f => f.path == path);\n * })\n * ```\n * \n * Note: The same caveats as `browser.execute` apply. The function will be stringified and then run inside Obsidian,\n * so you can't capture any local variables. E.g.\n * \n * This *won't* work:\n * ```ts\n * import { FileView } from Obsidian\n * browser.executeObsidian(({app, 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 this: WebdriverIO.Browser,\n func: (obs: ExecuteObsidianArg, ...params: Params) => Return,\n ...params: Params\n ): Promise<Return> {\n if (this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY].vault == undefined) {\n throw Error(\"No vault open\")\n }\n return await this.execute<Return, Params>(`\n const require = window.wdioObsidianService.require;\n return (${func.toString()}).call(null, {...window.wdioObsidianService}, ...arguments)\n `, ...params)\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.(m)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","stat","require","instructions","fs","id","resolve","path","browser","resolve","fsAsync","launcher","ObsidianLauncher"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wdio-obsidian-service",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A WebdriverIO service for end-to-end testing of Obsidian plugins",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"obsidian",
|
|
@@ -27,14 +27,15 @@
|
|
|
27
27
|
},
|
|
28
28
|
"repository": {
|
|
29
29
|
"type": "git",
|
|
30
|
-
"url": "git+https://github.com/jesse-r-s-hines/wdio-obsidian-service.git"
|
|
30
|
+
"url": "git+https://github.com/jesse-r-s-hines/wdio-obsidian-service.git",
|
|
31
|
+
"directory": "packages/wdio-obsidian-service"
|
|
31
32
|
},
|
|
32
33
|
"author": "jesse-r-s-hines",
|
|
33
34
|
"license": "MIT",
|
|
34
35
|
"bugs": {
|
|
35
36
|
"url": "https://github.com/jesse-r-s-hines/wdio-obsidian-service/issues"
|
|
36
37
|
},
|
|
37
|
-
"homepage": "https://jesse-r-s-hines.github.io/wdio-obsidian-service/
|
|
38
|
+
"homepage": "https://jesse-r-s-hines.github.io/wdio-obsidian-service/wdio-obsidian-service/README.html",
|
|
38
39
|
"devDependencies": {
|
|
39
40
|
"@types/chai": "^5.0.1",
|
|
40
41
|
"@types/lodash": "^4.17.14",
|
|
@@ -52,11 +53,11 @@
|
|
|
52
53
|
"tsup": "^8.3.5",
|
|
53
54
|
"tsx": "^4.19.2",
|
|
54
55
|
"typescript": "^5.8.2",
|
|
55
|
-
"wdio-obsidian-reporter": "^1.0
|
|
56
|
+
"wdio-obsidian-reporter": "^1.1.0"
|
|
56
57
|
},
|
|
57
58
|
"dependencies": {
|
|
58
59
|
"lodash": "^4.17.21",
|
|
59
|
-
"obsidian-launcher": "^1.0
|
|
60
|
+
"obsidian-launcher": "^1.1.0",
|
|
60
61
|
"semver": "^7.7.1"
|
|
61
62
|
},
|
|
62
63
|
"peerDependencies": {
|