wdio-obsidian-service 2.0.2 → 2.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/LICENSE +21 -0
- package/README.md +2 -2
- package/dist/index.d.ts +13 -5
- package/dist/index.js +147 -76
- package/dist/index.js.map +1 -1
- package/package.json +81 -81
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jesse Hines
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ WDIO Obsidian Service can:
|
|
|
8
8
|
- 📦 Sandbox Obsidian so tests don't interfere with your system or each other
|
|
9
9
|
- 📂 Open and switch between vaults
|
|
10
10
|
- 🛠️ Provide helper functions for common testing tasks
|
|
11
|
-
- 🤖 Run tests in
|
|
11
|
+
- 🤖 Run tests in CI
|
|
12
12
|
|
|
13
13
|
## Installation and Setup
|
|
14
14
|
|
|
@@ -157,7 +157,7 @@ To set the app version use `browserVersion` or `'wdio:obsidianOptions'.appVersio
|
|
|
157
157
|
- a specific version string like "1.7.7"
|
|
158
158
|
- "latest": run the latest non-beta Obsidian version
|
|
159
159
|
- "latest-beta": run the latest beta Obsidian version (or latest is there is no current beta)
|
|
160
|
-
- To download Obsidian beta versions you'll need to have an Obsidian Insiders account and either set the `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` env vars (`.env` file is supported) or pre-download the Obsidian beta with `npx obsidian-launcher download -v latest-beta`.
|
|
160
|
+
- To download Obsidian beta versions you'll need to have an Obsidian Insiders account and either set the `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` env vars (`.env` file is supported) or pre-download the Obsidian beta with `npx obsidian-launcher download app -v latest-beta`.
|
|
161
161
|
- "earliest": run the `minAppVersion` set in your plugin's `manifest.json`
|
|
162
162
|
|
|
163
163
|
To set the installer version use `'wdio:obsidianOptions'.installerVersion`. It can be set to one of:
|
package/dist/index.d.ts
CHANGED
|
@@ -353,7 +353,7 @@ interface ObsidianCapabilityOptions {
|
|
|
353
353
|
* - "latest-beta": Run the latest beta Obsidian version (or latest is there is no current beta)
|
|
354
354
|
* - To download Obsidian beta versions you'll need to have an Obsidian Insiders account and either set the
|
|
355
355
|
* `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` env vars (`.env` file is supported) or pre-download the Obsidian
|
|
356
|
-
* beta version with `npx obsidian-launcher download -v latest-beta`
|
|
356
|
+
* beta version with `npx obsidian-launcher download app -v latest-beta`
|
|
357
357
|
* - "earliest": Run the `minAppVersion` set in your `manifest.json`
|
|
358
358
|
*
|
|
359
359
|
* Defaults to "latest".
|
|
@@ -525,27 +525,35 @@ declare class ObsidianWorkerService implements Services.ServiceInstance {
|
|
|
525
525
|
* Sets up the --user-data-dir for the Electron app. Sets the obsidian.json in the dir to open the vault on boot.
|
|
526
526
|
*/
|
|
527
527
|
private electronSetupConfigDir;
|
|
528
|
+
/**
|
|
529
|
+
* Sets up the Obsidian app. Installs and launches it if needed, and sets permissions and contexts.
|
|
530
|
+
*
|
|
531
|
+
* You can configure Appium to install and launch the app for you. However, if you do that it reboots the app after
|
|
532
|
+
* every session/spec which is really slow. So we are handling the app setup manually here.
|
|
533
|
+
*/
|
|
534
|
+
private appiumSetupApp;
|
|
528
535
|
/**
|
|
529
536
|
* Opens the vault in appium.
|
|
530
537
|
*/
|
|
531
538
|
private appiumOpenVault;
|
|
532
539
|
/**
|
|
533
|
-
*
|
|
540
|
+
* Close any open vault and go back to the vault switcher
|
|
534
541
|
*/
|
|
535
|
-
private
|
|
542
|
+
private appiumCloseVault;
|
|
536
543
|
/**
|
|
537
544
|
* Waits for Obsidian to be ready, and does some other final setup.
|
|
538
545
|
*/
|
|
539
546
|
private prepareApp;
|
|
540
547
|
private createReloadObsidian;
|
|
541
548
|
/**
|
|
542
|
-
*
|
|
549
|
+
* Runs before the session and browser have started.
|
|
543
550
|
*/
|
|
544
551
|
beforeSession(config: Options.Testrunner, cap: WebdriverIO.Capabilities): Promise<void>;
|
|
545
552
|
/**
|
|
546
|
-
*
|
|
553
|
+
* Runs after session and browser have started, but before tests.
|
|
547
554
|
*/
|
|
548
555
|
before(cap: WebdriverIO.Capabilities, specs: unknown, browser: WebdriverIO.Browser): Promise<void>;
|
|
556
|
+
/** Runs after tests are done, but before the session is shut down */
|
|
549
557
|
after(result: number, cap: WebdriverIO.Capabilities): Promise<void>;
|
|
550
558
|
/**
|
|
551
559
|
* Cleanup
|
package/dist/index.js
CHANGED
|
@@ -16,11 +16,12 @@ import * as path2 from "path";
|
|
|
16
16
|
import * as fsAsync2 from "fs/promises";
|
|
17
17
|
import * as crypto2 from "crypto";
|
|
18
18
|
import { fileURLToPath } from "url";
|
|
19
|
+
import _2 from "lodash";
|
|
19
20
|
|
|
20
21
|
// src/types.ts
|
|
21
22
|
var OBSIDIAN_CAPABILITY_KEY = "wdio:obsidianOptions";
|
|
22
23
|
|
|
23
|
-
// ../../node_modules/@wdio/globals/build/index.js
|
|
24
|
+
// ../../node_modules/.pnpm/@wdio+globals@9.17.0_expect-webdriverio@5.4.1_webdriverio@9.18.4/node_modules/@wdio/globals/build/index.js
|
|
24
25
|
var globals = globalThis._wdioGlobals = globalThis._wdioGlobals || /* @__PURE__ */ new Map();
|
|
25
26
|
var GLOBALS_ERROR_MESSAGE = `No browser instance registered. Don't import @wdio/globals outside of the WDIO testrunner context. Or you have two two different "@wdio/globals" packages installed.`;
|
|
26
27
|
function proxyHandler(key) {
|
|
@@ -107,10 +108,8 @@ var BasePage = class {
|
|
|
107
108
|
import path from "path";
|
|
108
109
|
import crypto from "crypto";
|
|
109
110
|
import fsAsync from "fs/promises";
|
|
111
|
+
import * as tar from "tar";
|
|
110
112
|
import _ from "lodash";
|
|
111
|
-
async function sleep(ms) {
|
|
112
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
113
|
-
}
|
|
114
113
|
function quote(input) {
|
|
115
114
|
return `'${input.replace(/'/g, "'\\''")}'`;
|
|
116
115
|
}
|
|
@@ -130,47 +129,65 @@ function getAppiumOptions(cap) {
|
|
|
130
129
|
function isAppium(cap) {
|
|
131
130
|
return getAppiumOptions(cap).automationName?.toLocaleLowerCase() === "uiautomator2";
|
|
132
131
|
}
|
|
133
|
-
async function
|
|
132
|
+
async function appiumUploadFiles(browser2, opts) {
|
|
133
|
+
let {
|
|
134
|
+
src,
|
|
135
|
+
dest,
|
|
136
|
+
files = [src],
|
|
137
|
+
chunkSize = 2 * 1024 * 1024
|
|
138
|
+
} = opts;
|
|
134
139
|
src = path.resolve(src);
|
|
135
140
|
dest = path.posix.normalize(dest).replace(/\/$/, "");
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
files = files.map((f) => path.relative(src, f) || ".");
|
|
142
|
+
if (files.length == 0) return;
|
|
143
|
+
const tmpDir = "/data/local/tmp";
|
|
144
|
+
const slug = crypto.randomBytes(10).toString("base64url").replace(/[-_]/g, "0");
|
|
145
|
+
const stream = tar.create({ C: src, gzip: { level: 2 } }, files);
|
|
146
|
+
let buffers = [];
|
|
147
|
+
let bufferSize = 0;
|
|
148
|
+
let i = 0;
|
|
149
|
+
for await (const chunk of stream) {
|
|
150
|
+
if (bufferSize + chunk.length > chunkSize) {
|
|
151
|
+
const data2 = Buffer.concat(buffers).toString("base64");
|
|
152
|
+
await browser2.pushFile(`${tmpDir}/${slug}-${String(i).padStart(6, "0")}.tar`, data2);
|
|
153
|
+
i++;
|
|
154
|
+
buffers = [];
|
|
155
|
+
bufferSize = 0;
|
|
147
156
|
}
|
|
157
|
+
buffers.push(chunk);
|
|
158
|
+
bufferSize += chunk.length;
|
|
148
159
|
}
|
|
160
|
+
const data = Buffer.concat(buffers).toString("base64");
|
|
161
|
+
await browser2.pushFile(`${tmpDir}/${slug}-${String(i).padStart(6, "0")}.tar`, data);
|
|
162
|
+
await browser2.execute("mobile: shell", { command: "sh", args: ["-c", quote(`
|
|
163
|
+
mkdir -p ${quote(dest)};
|
|
164
|
+
cat ${tmpDir}/${slug}-*.tar | tar -xz -C ${quote(dest)};
|
|
165
|
+
rm ${tmpDir}/${slug}-*.tar;
|
|
166
|
+
`)] });
|
|
149
167
|
}
|
|
150
|
-
async function
|
|
151
|
-
src = path.resolve(src);
|
|
152
|
-
dest = path.posix.normalize(dest);
|
|
153
|
-
const slug = crypto.randomBytes(8).toString("base64url").replace(/[-_]/g, "0");
|
|
154
|
-
const tmpFile = `/data/local/tmp/upload-${slug}.tmp`;
|
|
155
|
-
const content = await fsAsync.readFile(src);
|
|
156
|
-
await browser2.pushFile(tmpFile, content.toString("base64"));
|
|
157
|
-
await browser2.execute("mobile: shell", { command: "mv", args: [tmpFile, quote(dest)] });
|
|
158
|
-
}
|
|
159
|
-
async function downloadFile(browser2, src, dest) {
|
|
168
|
+
async function appiumDownloadFile(browser2, src, dest) {
|
|
160
169
|
src = path.posix.normalize(src);
|
|
161
170
|
dest = path.resolve(dest);
|
|
162
171
|
const content = Buffer.from(await browser2.pullFile(src), "base64");
|
|
163
172
|
await fsAsync.writeFile(dest, content);
|
|
164
173
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
174
|
+
function normalizePath(p) {
|
|
175
|
+
p = p.replace(/([\\/])+/g, "/").replace(/(^\/+|\/+$)/g, "");
|
|
176
|
+
if (p == "") {
|
|
177
|
+
p = "/";
|
|
178
|
+
}
|
|
179
|
+
p = p.replace(/\u00A0|\u202F/g, " ");
|
|
180
|
+
p = p.normalize("NFC");
|
|
181
|
+
return p;
|
|
182
|
+
}
|
|
168
183
|
function isHidden(file) {
|
|
169
184
|
return file.split("/").some((p) => p.startsWith("."));
|
|
170
185
|
}
|
|
171
186
|
function isText(file) {
|
|
172
|
-
return [".md", ".json", ".txt", ".js"].includes(
|
|
187
|
+
return [".md", ".json", ".txt", ".js"].includes(path.extname(file).toLocaleLowerCase());
|
|
173
188
|
}
|
|
189
|
+
|
|
190
|
+
// src/pageobjects/obsidianPage.ts
|
|
174
191
|
var ObsidianPage = class extends BasePage {
|
|
175
192
|
getObsidianCapabilities() {
|
|
176
193
|
return this.browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];
|
|
@@ -456,6 +473,7 @@ var ObsidianPage = class extends BasePage {
|
|
|
456
473
|
}
|
|
457
474
|
}
|
|
458
475
|
} else {
|
|
476
|
+
vault = _2.mapKeys(vault, (v, k) => normalizePath(k));
|
|
459
477
|
for (const [file, content] of Object.entries(vault)) {
|
|
460
478
|
newVault.set(file, { type: "file", sourceContent: content });
|
|
461
479
|
}
|
|
@@ -587,7 +605,10 @@ var browserCommands = {
|
|
|
587
605
|
* @param id Id of the command to run.
|
|
588
606
|
*/
|
|
589
607
|
async executeObsidianCommand(id) {
|
|
590
|
-
const result = await this.executeObsidian(
|
|
608
|
+
const result = await this.executeObsidian(
|
|
609
|
+
({ app }, id2) => app.commands.executeCommandById(id2),
|
|
610
|
+
id
|
|
611
|
+
);
|
|
591
612
|
if (!result) {
|
|
592
613
|
throw Error(`Obsidian command ${id} not found or failed.`);
|
|
593
614
|
}
|
|
@@ -663,6 +684,9 @@ function selectThemes(currentThemes, selection) {
|
|
|
663
684
|
return currentThemes;
|
|
664
685
|
}
|
|
665
686
|
}
|
|
687
|
+
function getNormalizedObsidianOptions(cap) {
|
|
688
|
+
return cap[OBSIDIAN_CAPABILITY_KEY];
|
|
689
|
+
}
|
|
666
690
|
var minSupportedObsidianVersion = "1.0.3";
|
|
667
691
|
var ObsidianLauncherService = class {
|
|
668
692
|
constructor(options, capabilities, config) {
|
|
@@ -734,6 +758,9 @@ var ObsidianLauncherService = class {
|
|
|
734
758
|
cap["appium:app"] = apk;
|
|
735
759
|
cap["appium:chromedriverExecutableDir"] = chromedriverDir;
|
|
736
760
|
cap["wdio:enforceWebDriverClassic"] = true;
|
|
761
|
+
if (!getAppiumOptions(cap)["noReset"]) {
|
|
762
|
+
console.warn("Note: For best performance set noReset to true, wdio-obsidian-service will handle resetting Obsidian between tests.");
|
|
763
|
+
}
|
|
737
764
|
} else {
|
|
738
765
|
const [, installerVersion] = await this.obsidianLauncher.resolveVersion(
|
|
739
766
|
appVersion,
|
|
@@ -815,7 +842,7 @@ var ObsidianWorkerService = class {
|
|
|
815
842
|
* Creates a copy of the vault with plugins and themes installed
|
|
816
843
|
*/
|
|
817
844
|
async setupVault(cap) {
|
|
818
|
-
const obsidianOptions = cap
|
|
845
|
+
const obsidianOptions = getNormalizedObsidianOptions(cap);
|
|
819
846
|
let vaultCopy;
|
|
820
847
|
if (obsidianOptions.vault != void 0) {
|
|
821
848
|
log.info(`Opening vault ${obsidianOptions.vault}`);
|
|
@@ -835,7 +862,7 @@ var ObsidianWorkerService = class {
|
|
|
835
862
|
* Sets up the --user-data-dir for the Electron app. Sets the obsidian.json in the dir to open the vault on boot.
|
|
836
863
|
*/
|
|
837
864
|
async electronSetupConfigDir(cap) {
|
|
838
|
-
const obsidianOptions = cap
|
|
865
|
+
const obsidianOptions = getNormalizedObsidianOptions(cap);
|
|
839
866
|
const configDir = await this.obsidianLauncher.setupConfigDir({
|
|
840
867
|
appVersion: obsidianOptions.appVersion,
|
|
841
868
|
installerVersion: obsidianOptions.installerVersion,
|
|
@@ -857,15 +884,73 @@ var ObsidianWorkerService = class {
|
|
|
857
884
|
]
|
|
858
885
|
};
|
|
859
886
|
}
|
|
887
|
+
/**
|
|
888
|
+
* Sets up the Obsidian app. Installs and launches it if needed, and sets permissions and contexts.
|
|
889
|
+
*
|
|
890
|
+
* You can configure Appium to install and launch the app for you. However, if you do that it reboots the app after
|
|
891
|
+
* every session/spec which is really slow. So we are handling the app setup manually here.
|
|
892
|
+
*/
|
|
893
|
+
async appiumSetupApp() {
|
|
894
|
+
const browser2 = this.browser;
|
|
895
|
+
const appiumOptions = getAppiumOptions(browser2.requestedCapabilities);
|
|
896
|
+
const obsidianOptions = getNormalizedObsidianOptions(browser2.requestedCapabilities);
|
|
897
|
+
const appId = "md.obsidian";
|
|
898
|
+
const dumpsys = await browser2.execute("mobile: shell", { command: "dumpsys", args: ["package", appId] });
|
|
899
|
+
const installedObsidianVersion = dumpsys.match(/versionName[=:](.*)$/m)?.[1]?.trim();
|
|
900
|
+
if (installedObsidianVersion != obsidianOptions.appVersion) {
|
|
901
|
+
await browser2.execute("mobile: terminateApp", { appId });
|
|
902
|
+
await browser2.execute("mobile: removeApp", { appId, keepData: false });
|
|
903
|
+
await browser2.execute("mobile: installApp", {
|
|
904
|
+
appPath: appiumOptions.app,
|
|
905
|
+
// the APK
|
|
906
|
+
timeout: appiumOptions.androidInstallTimeout,
|
|
907
|
+
// respect appium configuration
|
|
908
|
+
grantPermissions: true
|
|
909
|
+
// this and autoGrantPermissions don't seem to really work, see below
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
const appState = await browser2.execute("mobile: queryAppState", { appId });
|
|
913
|
+
if (appState < 4) {
|
|
914
|
+
await browser2.execute("mobile: activateApp", { appId });
|
|
915
|
+
}
|
|
916
|
+
await browser2.execute("mobile: changePermissions", {
|
|
917
|
+
action: "grant",
|
|
918
|
+
appPackage: appId,
|
|
919
|
+
permissions: "all"
|
|
920
|
+
});
|
|
921
|
+
await browser2.execute("mobile: changePermissions", {
|
|
922
|
+
action: "allow",
|
|
923
|
+
appPackage: appId,
|
|
924
|
+
permissions: [
|
|
925
|
+
// these are from apk AndroidManifest.xml (extracted with apktool)
|
|
926
|
+
"READ_EXTERNAL_STORAGE",
|
|
927
|
+
"WRITE_EXTERNAL_STORAGE",
|
|
928
|
+
"MANAGE_EXTERNAL_STORAGE"
|
|
929
|
+
],
|
|
930
|
+
target: "appops"
|
|
931
|
+
// requires appium --allow-insecure adb_shell
|
|
932
|
+
});
|
|
933
|
+
const context = "WEBVIEW_md.obsidian";
|
|
934
|
+
await browser2.waitUntil(async () => (await browser2.getContexts()).includes(context));
|
|
935
|
+
await browser2.switchContext(context);
|
|
936
|
+
await browser2.execute(() => {
|
|
937
|
+
if (window.location.href != "http://localhost/") {
|
|
938
|
+
window.location.replace("http://localhost/");
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
if (!obsidianOptions.vault) {
|
|
942
|
+
await this.appiumCloseVault();
|
|
943
|
+
}
|
|
944
|
+
}
|
|
860
945
|
/**
|
|
861
946
|
* Opens the vault in appium.
|
|
862
947
|
*/
|
|
863
948
|
async appiumOpenVault() {
|
|
864
949
|
const browser2 = this.browser;
|
|
865
|
-
const obsidianOptions = browser2.requestedCapabilities
|
|
950
|
+
const obsidianOptions = getNormalizedObsidianOptions(browser2.requestedCapabilities);
|
|
866
951
|
const androidVault = `${this.androidVaultDir}/${path3.basename(obsidianOptions.vaultCopy)}`;
|
|
867
952
|
obsidianOptions.uploadedVault = androidVault;
|
|
868
|
-
await
|
|
953
|
+
await appiumUploadFiles(browser2, { src: obsidianOptions.vaultCopy, dest: androidVault });
|
|
869
954
|
await browser2.execute(async (androidVault2) => {
|
|
870
955
|
localStorage.clear();
|
|
871
956
|
localStorage.setItem("mobile-external-vaults", JSON.stringify([androidVault2]));
|
|
@@ -875,35 +960,23 @@ var ObsidianWorkerService = class {
|
|
|
875
960
|
}, androidVault);
|
|
876
961
|
}
|
|
877
962
|
/**
|
|
878
|
-
*
|
|
963
|
+
* Close any open vault and go back to the vault switcher
|
|
879
964
|
*/
|
|
880
|
-
async
|
|
965
|
+
async appiumCloseVault() {
|
|
881
966
|
const browser2 = this.browser;
|
|
882
|
-
await browser2.execute(
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
await browser2.execute("mobile: changePermissions", {
|
|
888
|
-
action: "allow",
|
|
889
|
-
appPackage: "md.obsidian",
|
|
890
|
-
permissions: [
|
|
891
|
-
// these are from apk AndroidManifest.xml (extracted with apktool)
|
|
892
|
-
"READ_EXTERNAL_STORAGE",
|
|
893
|
-
"WRITE_EXTERNAL_STORAGE",
|
|
894
|
-
"MANAGE_EXTERNAL_STORAGE"
|
|
895
|
-
],
|
|
896
|
-
target: "appops"
|
|
897
|
-
// requires appium --allow-insecure adb_shell
|
|
967
|
+
await browser2.execute(async () => {
|
|
968
|
+
if (localStorage.length > 0) {
|
|
969
|
+
localStorage.clear();
|
|
970
|
+
location.reload();
|
|
971
|
+
}
|
|
898
972
|
});
|
|
899
|
-
await browser2.switchContext("WEBVIEW_md.obsidian");
|
|
900
973
|
}
|
|
901
974
|
/**
|
|
902
975
|
* Waits for Obsidian to be ready, and does some other final setup.
|
|
903
976
|
*/
|
|
904
977
|
async prepareApp() {
|
|
905
978
|
const browser2 = this.browser;
|
|
906
|
-
const obsidianOptions = browser2.requestedCapabilities
|
|
979
|
+
const obsidianOptions = getNormalizedObsidianOptions(browser2.requestedCapabilities);
|
|
907
980
|
if (obsidianOptions.emulateMobile && obsidianOptions.vault != void 0) {
|
|
908
981
|
await browser2.waitUntil(() => browser2.execute(() => !!window.electron));
|
|
909
982
|
const [width, height] = await browser2.execute(() => [window.innerWidth, window.innerHeight]);
|
|
@@ -931,7 +1004,7 @@ var ObsidianWorkerService = class {
|
|
|
931
1004
|
createReloadObsidian() {
|
|
932
1005
|
const service = this;
|
|
933
1006
|
const reloadObsidian = async function({ vault, plugins, theme } = {}) {
|
|
934
|
-
const oldObsidianOptions = this.requestedCapabilities
|
|
1007
|
+
const oldObsidianOptions = getNormalizedObsidianOptions(this.requestedCapabilities);
|
|
935
1008
|
const selectedPlugins = selectPlugins(oldObsidianOptions.plugins, plugins);
|
|
936
1009
|
const selectedThemes = selectThemes(oldObsidianOptions.themes, theme);
|
|
937
1010
|
if (!vault && oldObsidianOptions.vaultCopy == void 0) {
|
|
@@ -953,16 +1026,15 @@ var ObsidianWorkerService = class {
|
|
|
953
1026
|
await this.execute(() => {
|
|
954
1027
|
window.location.replace("http://localhost/_capacitor_file_/not-a-file");
|
|
955
1028
|
});
|
|
956
|
-
await sleep(2e3);
|
|
957
1029
|
const local = path3.join(oldObsidianOptions.vaultCopy, ".obsidian");
|
|
958
1030
|
const localCommunityPlugins = path3.join(local, "community-plugins.json");
|
|
959
1031
|
const localAppearance = path3.join(local, "appearance.json");
|
|
960
1032
|
const remote2 = `${oldObsidianOptions.uploadedVault}/.obsidian`;
|
|
961
1033
|
const remoteCommunityPlugins = `${remote2}/community-plugins.json`;
|
|
962
1034
|
const remoteAppearance = `${remote2}/appearance.json`;
|
|
963
|
-
await
|
|
1035
|
+
await appiumDownloadFile(this, remoteCommunityPlugins, localCommunityPlugins).catch(() => {
|
|
964
1036
|
});
|
|
965
|
-
await
|
|
1037
|
+
await appiumDownloadFile(this, remoteAppearance, localAppearance).catch(() => {
|
|
966
1038
|
});
|
|
967
1039
|
await service.obsidianLauncher.setupVault({
|
|
968
1040
|
vault: oldObsidianOptions.vaultCopy,
|
|
@@ -970,12 +1042,9 @@ var ObsidianWorkerService = class {
|
|
|
970
1042
|
plugins: selectedPlugins,
|
|
971
1043
|
themes: selectedThemes
|
|
972
1044
|
});
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
}
|
|
976
|
-
if (await fileExists(localAppearance)) {
|
|
977
|
-
await uploadFile(this, localAppearance, remoteAppearance);
|
|
978
|
-
}
|
|
1045
|
+
let files = [localCommunityPlugins, localAppearance];
|
|
1046
|
+
files = (await Promise.all(files.map(async (f) => await fileExists(f) ? f : ""))).filter((f) => f);
|
|
1047
|
+
await appiumUploadFiles(this, { src: local, dest: remote2, files });
|
|
979
1048
|
await this.execute(() => {
|
|
980
1049
|
window.location.replace("http://localhost/");
|
|
981
1050
|
});
|
|
@@ -990,7 +1059,7 @@ var ObsidianWorkerService = class {
|
|
|
990
1059
|
await service.electronSetupConfigDir(newCap);
|
|
991
1060
|
await this.reloadSession(newCap);
|
|
992
1061
|
} else {
|
|
993
|
-
await
|
|
1062
|
+
await this.pause(2e3);
|
|
994
1063
|
await this.deleteSession({ shutdownDriver: false });
|
|
995
1064
|
await service.obsidianLauncher.setupVault({
|
|
996
1065
|
vault: oldObsidianOptions.vaultCopy,
|
|
@@ -1006,7 +1075,7 @@ var ObsidianWorkerService = class {
|
|
|
1006
1075
|
return reloadObsidian;
|
|
1007
1076
|
}
|
|
1008
1077
|
/**
|
|
1009
|
-
*
|
|
1078
|
+
* Runs before the session and browser have started.
|
|
1010
1079
|
*/
|
|
1011
1080
|
async beforeSession(config, cap) {
|
|
1012
1081
|
try {
|
|
@@ -1022,7 +1091,7 @@ var ObsidianWorkerService = class {
|
|
|
1022
1091
|
}
|
|
1023
1092
|
}
|
|
1024
1093
|
/**
|
|
1025
|
-
*
|
|
1094
|
+
* Runs after session and browser have started, but before tests.
|
|
1026
1095
|
*/
|
|
1027
1096
|
async before(cap, specs, browser2) {
|
|
1028
1097
|
this.browser = browser2;
|
|
@@ -1036,7 +1105,7 @@ var ObsidianWorkerService = class {
|
|
|
1036
1105
|
browser2[name] = cmd;
|
|
1037
1106
|
}
|
|
1038
1107
|
if (isAppium(browser2.requestedCapabilities)) {
|
|
1039
|
-
await this.
|
|
1108
|
+
await this.appiumSetupApp();
|
|
1040
1109
|
if (cap[OBSIDIAN_CAPABILITY_KEY].vault) {
|
|
1041
1110
|
await this.appiumOpenVault();
|
|
1042
1111
|
}
|
|
@@ -1046,15 +1115,13 @@ var ObsidianWorkerService = class {
|
|
|
1046
1115
|
throw new SevereServiceError(getServiceErrorMessage(e));
|
|
1047
1116
|
}
|
|
1048
1117
|
}
|
|
1118
|
+
/** Runs after tests are done, but before the session is shut down */
|
|
1049
1119
|
async after(result, cap) {
|
|
1050
1120
|
const browser2 = this.browser;
|
|
1121
|
+
if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;
|
|
1051
1122
|
if (isAppium(cap)) {
|
|
1052
|
-
|
|
1053
|
-
await browser2.execute("mobile:
|
|
1054
|
-
await browser2.execute("mobile: shell", {
|
|
1055
|
-
command: "rm",
|
|
1056
|
-
args: ["-rf", this.androidVaultDir]
|
|
1057
|
-
});
|
|
1123
|
+
await this.appiumCloseVault();
|
|
1124
|
+
await browser2.execute("mobile: shell", { command: "rm", args: ["-rf", this.androidVaultDir] });
|
|
1058
1125
|
}
|
|
1059
1126
|
}
|
|
1060
1127
|
/**
|
|
@@ -1075,7 +1142,11 @@ async function startWdioSession(params, serviceOptions) {
|
|
|
1075
1142
|
const testRunnerOptions = {
|
|
1076
1143
|
cacheDir: params.cacheDir
|
|
1077
1144
|
};
|
|
1078
|
-
const launcherService = new ObsidianLauncherService(
|
|
1145
|
+
const launcherService = new ObsidianLauncherService(
|
|
1146
|
+
serviceOptions,
|
|
1147
|
+
[capabilities],
|
|
1148
|
+
testRunnerOptions
|
|
1149
|
+
);
|
|
1079
1150
|
const workerService = new ObsidianWorkerService(serviceOptions, capabilities, testRunnerOptions);
|
|
1080
1151
|
await launcherService.onPrepare(testRunnerOptions, [capabilities]);
|
|
1081
1152
|
await workerService.beforeSession(testRunnerOptions, capabilities);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/service.ts","../src/pageobjects/obsidianPage.ts","../src/types.ts","../../../node_modules/@wdio/globals/build/index.js","../src/pageobjects/basePage.ts","../src/utils.ts","../src/browserCommands.ts","../src/standalone.ts"],"sourcesContent":["/**\n * @module\n * @document ../README.md\n * @categoryDescription Options\n * Capability and service options.\n * @categoryDescription WDIO Helpers\n * Helpers for use in wdio.conf.mts, or for launching WDIO in standalone mode.\n * @categoryDescription Utilities\n * Browser commands and helper functions for writing tests.\n */\nimport ObsidianLauncher from \"obsidian-launcher\";\nimport { deprecate } from \"util\";\n\nimport { ObsidianLauncherService, ObsidianWorkerService } from \"./service.js\";\n/** @hidden */\nexport default ObsidianWorkerService;\n/** @hidden */\nexport const launcher = ObsidianLauncherService;\n\nexport type { ObsidianCapabilityOptions, ObsidianServiceOptions } from \"./types.js\";\nexport type { ObsidianBrowserCommands, ExecuteObsidianArg, InstalledPlugins } from \"./browserCommands.js\";\nexport { default as obsidianPage } from \"./pageobjects/obsidianPage.js\";\nexport type { ObsidianPage, Platform } from \"./pageobjects/obsidianPage.js\";\nexport type { PluginEntry, ThemeEntry } from \"obsidian-launcher\";\n\nexport { minSupportedObsidianVersion } from \"./service.js\";\nexport { startWdioSession } from \"./standalone.js\";\n\n// Some convenience helpers for use in wdio.conf.mts\n\n/**\n * Returns true if there is a current Obsidian beta and we have the credentials to download it, or its already in cache.\n * @category WDIO Helpers\n * @param opts.cacheDir Obsidian cache dir, defaults to `.obsidian-cache`.\n */\nexport async function obsidianBetaAvailable(opts: string|{cacheDir?: string} = {}) {\n opts = typeof opts == \"string\" ? {cacheDir: opts} : opts;\n const launcher = new ObsidianLauncher(opts);\n const versionInfo = await launcher.getVersionInfo(\"latest-beta\");\n return versionInfo.isBeta && await launcher.isAvailable(versionInfo.version);\n}\n\n/**\n * Resolves Obsidian app and installer version strings to absolute versions.\n * \n * @category WDIO Helpers\n * @deprecated Use parseObsidianVersions instead\n */\nexport const resolveObsidianVersions = deprecate(async function(\n appVersion: string, installerVersion: string, cacheDir?: string,\n): Promise<[string, string]> {\n const launcher = new ObsidianLauncher({cacheDir: cacheDir});\n return await launcher.resolveVersion(appVersion, installerVersion);\n}, 'resolveObsidianVersions is deprecated, use parseObsidianVersions instead');\n\n\n/**\n * Parses a string of Obsidian versions into [appVersion, installerVersion] tuples. This is a convenience helper for use\n * in `wdio.conf.mts`\n * \n * `versions` should be a space separated list of Obsidian app versions. You can optionally specify the installer\n * version by using \"appVersion/installerVersion\" e.g. `\"1.7.7/1.8.10\"`.\n * \n * Example: \n * ```js\n * parseObsidianVersions(\"1.8.10/1.7.7 latest latest-beta/earliest\")\n * ```\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n * \n * @category WDIO Helpers\n * @param versions string to parse\n * @param opts.cacheDir Obsidian cache dir, defaults to `.obsidian-cache`.\n * @returns [appVersion, installerVersion][] resolved to specific versions.\n */\nexport async function parseObsidianVersions(\n versions: string,\n opts: {cacheDir?: string} = {},\n): Promise<[string, string][]> {\n const launcher = new ObsidianLauncher({cacheDir: opts.cacheDir});\n return launcher.parseVersions(versions);\n}\n","import fs from \"fs\"\nimport fsAsync from \"fs/promises\"\nimport path from \"path\"\nimport { SevereServiceError } from 'webdriverio'\nimport type { Capabilities, Options, Services } from '@wdio/types'\nimport logger from '@wdio/logger'\nimport { fileURLToPath } from \"url\"\nimport ObsidianLauncher, {\n PluginEntry, ThemeEntry, DownloadedPluginEntry, DownloadedThemeEntry,\n} from \"obsidian-launcher\"\nimport { browserCommands } from \"./browserCommands.js\"\nimport {\n ObsidianServiceOptions, NormalizedObsidianCapabilityOptions, OBSIDIAN_CAPABILITY_KEY,\n} from \"./types.js\"\nimport { sleep, isAppium, uploadFolder, downloadFile, uploadFile, getAppiumOptions, fileExists } from \"./utils.js\";\nimport semver from \"semver\"\nimport _ from \"lodash\"\n\n\nconst log = logger(\"wdio-obsidian-service\");\n\nfunction getDefaultCacheDir(rootDir: string) {\n return path.resolve(rootDir, process.env.WEBDRIVER_CACHE_DIR ?? process.env.OBSIDIAN_CACHE ?? \"./.obsidian-cache\");\n}\n\n/** By default wdio continues on service errors, so we throw a SevereServiceError to make it bail on error */\nfunction getServiceErrorMessage(e: any) {\n return (\n `Failed to download and setup Obsidian. Caused by:\\n` +\n `${e.stack}\\n`+\n ` ------The above causes:-----`\n );\n}\n\nfunction resolveEntry(rootDir: string, entry: PluginEntry|ThemeEntry): PluginEntry|ThemeEntry {\n if (typeof entry == \"string\") {\n return path.resolve(rootDir, entry);\n } else if ('path' in entry) {\n return {...entry, path: path.resolve(rootDir, entry.path)};\n } else {\n return entry;\n }\n}\n\n/** Returns a plugin list with only the selected plugin ids enabled. */\nfunction selectPlugins(currentPlugins: DownloadedPluginEntry[], selection?: string[]): DownloadedPluginEntry[] {\n if (selection !== undefined) {\n const unknownPlugins = _.difference(selection, currentPlugins.map(p => p.id));\n if (unknownPlugins.length > 0) {\n throw Error(`Unknown plugin ids: ${unknownPlugins.join(', ')}`)\n }\n return currentPlugins.map(p => ({\n ...p,\n enabled: selection.includes(p.id) || p.id == \"wdio-obsidian-service-plugin\",\n }));\n } else {\n return currentPlugins;\n }\n}\n\n/** Returns a theme list with only the selected theme enabled. */\nfunction selectThemes(currentThemes: DownloadedThemeEntry[], selection?: string): DownloadedThemeEntry[] {\n if (selection !== undefined) {\n if (selection != \"default\" && currentThemes.every((t: any) => t.name != selection)) {\n throw Error(`Unknown theme: ${selection}`);\n }\n return currentThemes.map((t: any) => ({...t, enabled: selection != 'default' && t.name === selection}));\n } else {\n return currentThemes;\n }\n}\n\n\n/**\n * Minimum Obsidian version that wdio-obsidian-service supports.\n * @category WDIO Helpers\n */\nexport const minSupportedObsidianVersion: string = \"1.0.3\"\n\n\n/**\n * wdio launcher service.\n * Use in wdio.conf.mts like so:\n * ```ts\n * services: ['obsidian'],\n * ```\n * @hidden\n */\nexport class ObsidianLauncherService implements Services.ServiceInstance {\n private obsidianLauncher: ObsidianLauncher\n private readonly helperPluginPath: string\n private readonly rootDir: string\n\n constructor (\n public options: ObsidianServiceOptions,\n public capabilities: WebdriverIO.Capabilities,\n public config: Options.Testrunner\n ) {\n this.rootDir = config.rootDir || process.cwd();\n this.obsidianLauncher = new ObsidianLauncher({\n cacheDir: config.cacheDir ?? getDefaultCacheDir(this.rootDir),\n versionsUrl: options.versionsUrl,\n communityPluginsUrl: options.communityPluginsUrl, communityThemesUrl: options.communityThemesUrl,\n });\n this.helperPluginPath = path.resolve(fileURLToPath(import.meta.url), '../../helper-plugin');\n }\n\n /**\n * Validates wdio:obsidianOptions and downloads Obsidian, plugins, and themes.\n */\n async onPrepare(config: Options.Testrunner, capabilities: Capabilities.TestrunnerCapabilities) {\n try {\n if (!Array.isArray(capabilities)) {\n capabilities = Object.values(capabilities).map(\n (multiremoteOption) => (multiremoteOption as Capabilities.WithRequestedCapabilities).capabilities,\n );\n }\n\n const obsidianCapabilities = capabilities.flatMap((cap) => {\n if ((\"browserName\" in cap) && cap.browserName === \"obsidian\") {\n return [cap as WebdriverIO.Capabilities];\n } else {\n return [];\n }\n });\n\n for (const cap of obsidianCapabilities) {\n const obsidianOptions = cap[OBSIDIAN_CAPABILITY_KEY] ?? {};\n \n // check vault\n const vault = obsidianOptions.vault ? path.resolve(this.rootDir, obsidianOptions.vault) : undefined;\n if (vault && !fs.existsSync(vault)) {\n throw Error(`Vault \"${vault}\" doesn't exist`)\n }\n \n // download plugins and themes to cache\n const plugins = await this.obsidianLauncher.downloadPlugins(\n (obsidianOptions.plugins ?? [])\n .concat([this.helperPluginPath]) // Always install the helper plugin\n .map(p => resolveEntry(this.rootDir, p) as PluginEntry)\n );\n\n const themes = await this.obsidianLauncher.downloadThemes(\n (obsidianOptions.themes ?? [])\n .map(t => resolveEntry(this.rootDir, t) as ThemeEntry),\n );\n\n let appVersion = cap.browserVersion ?? cap[OBSIDIAN_CAPABILITY_KEY]?.appVersion ?? \"latest\";\n appVersion = (await this.obsidianLauncher.getVersionInfo(appVersion)).version;\n if (semver.lt(appVersion, minSupportedObsidianVersion)) {\n throw Error(`Minimum supported Obsidian version is ${minSupportedObsidianVersion}`)\n }\n\n if (isAppium(cap)) {\n let apk = getAppiumOptions(cap).app;\n if (!apk) {\n apk = await this.obsidianLauncher.downloadAndroid(appVersion);\n }\n let chromedriverDir = getAppiumOptions(cap).chromedriverExecutableDir;\n if (!chromedriverDir) {\n chromedriverDir = path.join(this.obsidianLauncher.cacheDir, 'appium-chromedriver');\n }\n\n const normalizedObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...obsidianOptions,\n plugins, themes, vault: vault,\n appVersion, installerVersion: appVersion,\n emulateMobile: false,\n }\n cap[OBSIDIAN_CAPABILITY_KEY] = normalizedObsidianOptions;\n cap['appium:app'] = apk;\n cap['appium:chromedriverExecutableDir'] = chromedriverDir;\n cap[\"wdio:enforceWebDriverClassic\"] = true; // BiDi doesn't seem to work on Obsidian mobile\n } else {\n const [, installerVersion] = await this.obsidianLauncher.resolveVersion(\n appVersion, obsidianOptions.installerVersion ?? \"earliest\",\n );\n const installerInfo = await this.obsidianLauncher.getInstallerInfo(installerVersion);\n\n let installerPath: string;\n if (obsidianOptions.binaryPath) {\n installerPath = path.resolve(this.rootDir, obsidianOptions.binaryPath)\n } else {\n installerPath = await this.obsidianLauncher.downloadInstaller(installerVersion);\n }\n let appPath: string;\n if (obsidianOptions.appPath) {\n appPath = path.resolve(this.rootDir, obsidianOptions.appPath)\n } else {\n appPath = await this.obsidianLauncher.downloadApp(appVersion);\n }\n let chromedriverPath = cap['wdio:chromedriverOptions']?.binary;\n // wdio can't download chromedriver for versions less than 115 automatically. Fetching it ourselves is\n // also a bit faster as it skips the chromedriver version detection step.\n if (!chromedriverPath) {\n chromedriverPath = await this.obsidianLauncher.downloadChromedriver(installerVersion);\n }\n\n const normalizedObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...obsidianOptions,\n plugins, themes, vault,\n binaryPath: installerPath, appPath: appPath,\n appVersion, installerVersion,\n emulateMobile: obsidianOptions.emulateMobile ?? false,\n }\n\n cap.browserName = \"chrome\";\n cap.browserVersion = installerInfo.chrome;\n cap[OBSIDIAN_CAPABILITY_KEY] = normalizedObsidianOptions;\n cap['goog:chromeOptions'] = {\n binary: installerPath,\n windowTypes: [\"app\", \"webview\"],\n ...cap['goog:chromeOptions'],\n args: [\n // Workaround for SUID issue on linux. See https://github.com/electron/electron/issues/42510\n ...(process.platform == 'linux' ? [\"--no-sandbox\"] : []),\n ...(cap['goog:chromeOptions']?.args ?? [])\n ],\n }\n cap['wdio:chromedriverOptions'] = {\n // allowedIps is not included in the types, but gets passed as --allowed-ips to chromedriver.\n // It defaults to [\"0.0.0.0\"] which makes Windows Firewall complain, and we don't need remote\n // connections anyways.\n allowedIps: [],\n ...cap['wdio:chromedriverOptions'],\n binary: chromedriverPath,\n } as any\n cap[\"wdio:enforceWebDriverClassic\"] = true; // electron doesn't support BiDi yet.\n }\n }\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n}\n\n\n/**\n * wdio worker service.\n * Use in wdio.conf.mts like so:\n * ```ts\n * services: ['obsidian'],\n * ```\n * @hidden\n */\nexport class ObsidianWorkerService implements Services.ServiceInstance {\n private obsidianLauncher: ObsidianLauncher\n private browser: WebdriverIO.Browser|undefined;\n /** Directories to clean up after the tests */\n private tmpDirs: string[]\n /** Path on Android devices to store temporary vaults */\n private androidVaultDir = \"/storage/emulated/0/Documents/wdio-obsidian-service-vaults\";\n\n constructor (\n public options: ObsidianServiceOptions,\n public capabilities: WebdriverIO.Capabilities,\n public config: Options.Testrunner\n ) {\n this.obsidianLauncher = new ObsidianLauncher({\n cacheDir: config.cacheDir ?? getDefaultCacheDir(config.rootDir || process.cwd()),\n versionsUrl: options.versionsUrl,\n communityPluginsUrl: options.communityPluginsUrl, communityThemesUrl: options.communityThemesUrl,\n });\n this.tmpDirs = [];\n }\n\n /**\n * Creates a copy of the vault with plugins and themes installed\n */\n private async setupVault(cap: WebdriverIO.Capabilities) {\n const obsidianOptions = cap[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n let vaultCopy: string|undefined;\n if (obsidianOptions.vault != undefined) {\n log.info(`Opening vault ${obsidianOptions.vault}`);\n vaultCopy = await this.obsidianLauncher.setupVault({\n vault: obsidianOptions.vault,\n copy: true,\n plugins: obsidianOptions.plugins,\n themes: obsidianOptions.themes,\n });\n this.tmpDirs.push(vaultCopy);\n } else {\n log.info(`Opening Obsidian without a vault`)\n }\n obsidianOptions.vaultCopy = vaultCopy; // for use in getVaultPath() and the other service hooks\n }\n\n /**\n * Sets up the --user-data-dir for the Electron app. Sets the obsidian.json in the dir to open the vault on boot.\n */\n private async electronSetupConfigDir(cap: WebdriverIO.Capabilities) {\n const obsidianOptions = cap[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n const configDir = await this.obsidianLauncher.setupConfigDir({\n appVersion: obsidianOptions.appVersion, installerVersion: obsidianOptions.installerVersion,\n appPath: obsidianOptions.appPath,\n vault: obsidianOptions.vaultCopy,\n // `app.emulateMobile` just sets this localStorage variable. Setting it ourselves here instead of calling\n // the function simplifies the boot/plugin load sequence and makes sure plugins load in mobile mode.\n localStorage: obsidianOptions.emulateMobile ? {\"EmulateMobile\": \"1\"} : {},\n });\n this.tmpDirs.push(configDir);\n\n cap['goog:chromeOptions'] = {\n ...cap['goog:chromeOptions'],\n args: [\n `--user-data-dir=${configDir}`,\n ...(cap['goog:chromeOptions']?.args ?? []).filter(arg => {\n const match = arg.match(/^--user-data-dir=(.*)$/);\n return !match || !this.tmpDirs.includes(match[1]);\n })\n ]\n }\n }\n\n /**\n * Opens the vault in appium.\n */\n private async appiumOpenVault() {\n const browser = this.browser!;\n const obsidianOptions: NormalizedObsidianCapabilityOptions = browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];\n const androidVault = `${this.androidVaultDir}/${path.basename(obsidianOptions.vaultCopy!)}`;\n // TODO: Capabilities is not really the right place to be storing state like vaultCopy and uploadVault\n obsidianOptions.uploadedVault = androidVault;\n // transfer the vault to the device\n await uploadFolder(browser, obsidianOptions.vaultCopy!, androidVault);\n\n // open vault by setting the localStorage keys and relaunching Obsidian\n // on appium restarting the app with appium:fullReset is *really* slow. And, unlike electron we can actually\n // switch vault with just a reload. So for Appium, instead of rebooting we manually wipe localStorage and use\n // reload to switch the vault.\n await browser.execute(async (androidVault) => {\n localStorage.clear();\n localStorage.setItem('mobile-external-vaults', JSON.stringify([androidVault]));\n localStorage.setItem('mobile-selected-vault', androidVault);\n // appId on mobile is just the full vault path\n localStorage.setItem(`enable-plugin-${androidVault}`, 'true');\n window.location.reload();\n }, androidVault);\n }\n\n /**\n * Sets appium app permissions and context\n */\n private async appiumSetContext() {\n const browser = this.browser!;\n // grant Obsidian the permissions it needs, mainly file access.\n // appium:autoGrantPermissions is supposed to automatically grant everything it needs, but I can't get it to\n // work. The \"mobile: changePermissions\" \"all\" option also doesn't seem to work here. I have to explicitly list\n // the permissions and use the \"appops\" target. See https://github.com/appium/appium/issues/19991\n // I'm calling \"mobile: changePermissions\" \"all\" as well just in case it is actually doing something\n\n await browser.execute(\"mobile: changePermissions\", {\n action: \"grant\",\n appPackage: \"md.obsidian\",\n permissions: \"all\",\n });\n\n await browser.execute(\"mobile: changePermissions\", {\n action: \"allow\",\n appPackage: \"md.obsidian\",\n permissions: [ // these are from apk AndroidManifest.xml (extracted with apktool)\n \"READ_EXTERNAL_STORAGE\", \"WRITE_EXTERNAL_STORAGE\", \"MANAGE_EXTERNAL_STORAGE\",\n ],\n target: \"appops\", // requires appium --allow-insecure adb_shell\n });\n\n await browser.switchContext(\"WEBVIEW_md.obsidian\");\n }\n\n /**\n * Waits for Obsidian to be ready, and does some other final setup.\n */\n private async prepareApp() {\n const browser = this.browser!;\n const obsidianOptions: NormalizedObsidianCapabilityOptions = browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];\n\n if (obsidianOptions.emulateMobile && obsidianOptions.vault != undefined) {\n // I don't think this is technically necessary, but when you set the window size via emulateMobile it sets\n // the window size in the browser view, but not the actual window size which looks weird and makes it hard\n // to manually debug a paused test. The normal ways to set window size don't work on Obsidian. Obsidian\n // doesn't respect the `--window-size` argument, and wdio setViewport and setWindowSize don't work without\n // BiDi. This resizes the window directly using electron APIs.\n await browser.waitUntil(() => browser.execute(() => !!(window as any).electron));\n const [width, height] = await browser.execute(() => [window.innerWidth, window.innerHeight]);\n await browser.execute(async (width, height) => {\n await (window as any).electron.remote.getCurrentWindow().setSize(width, height);\n }, width, height);\n }\n\n // wait until app is loaded\n if (obsidianOptions.vault) {\n await browser.waitUntil( // wait until the helper plugin is loaded\n () => browser.execute(() => !!(window as any).wdioObsidianService),\n {timeout: 30 * 1000, interval: 100},\n );\n await browser.executeObsidian(async ({app}) => {\n await new Promise<void>((resolve) => app.workspace.onLayoutReady(resolve));\n });\n } else {\n await browser.execute(async () => {\n if (document.readyState === \"loading\") {\n return new Promise<void>(resolve => document.addEventListener(\"DOMContentLoaded\", () => resolve()));\n }\n });\n }\n }\n\n private createReloadObsidian() {\n const service = this; // eslint-disable-line @typescript-eslint/no-this-alias\n const reloadObsidian: WebdriverIO.Browser['reloadObsidian'] = async function(\n this: WebdriverIO.Browser,\n {vault, plugins, theme} = {},\n ) {\n const oldObsidianOptions: NormalizedObsidianCapabilityOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];\n const selectedPlugins = selectPlugins(oldObsidianOptions.plugins, plugins);\n const selectedThemes = selectThemes(oldObsidianOptions.themes, theme);\n if (!vault && oldObsidianOptions.vaultCopy == undefined) {\n throw Error(`No vault is open, pass a vault path to reloadObsidian`);\n }\n const newObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...oldObsidianOptions,\n // Resolve relative to PWD instead of root dir during tests\n vault: vault ? path.resolve(vault) : oldObsidianOptions.vault,\n plugins: selectedPlugins, themes: selectedThemes,\n };\n\n if (isAppium(this.requestedCapabilities)) {\n this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] = newObsidianOptions;\n if (vault) {\n await service.setupVault(this.requestedCapabilities);\n await service.appiumOpenVault();\n } else {\n // reload without resetting app state or triggering appium:fullReset\n\n // hack to disable the Obsidian app and make sure it doesn't write to the vault while we modify it\n await this.execute(() => {\n window.location.replace('http://localhost/_capacitor_file_/not-a-file');\n })\n\n await sleep(2000)\n\n // while Obsidian is down, modify the vault files to setup plugins and themes\n const local = path.join(oldObsidianOptions.vaultCopy!, \".obsidian\");\n const localCommunityPlugins = path.join(local, \"community-plugins.json\");\n const localAppearance = path.join(local, \"appearance.json\");\n const remote = `${oldObsidianOptions.uploadedVault!}/.obsidian`;\n const remoteCommunityPlugins = `${remote}/community-plugins.json`;\n const remoteAppearance = `${remote}/appearance.json`;\n\n await downloadFile(this, remoteCommunityPlugins, localCommunityPlugins).catch(() => {});\n await downloadFile(this, remoteAppearance, localAppearance).catch(() => {});\n await service.obsidianLauncher.setupVault({\n vault: oldObsidianOptions.vaultCopy!, copy: false,\n plugins: selectedPlugins, themes: selectedThemes,\n });\n if (await fileExists(localCommunityPlugins)) {\n await uploadFile(this, localCommunityPlugins, remoteCommunityPlugins);\n }\n if (await fileExists(localAppearance)) {\n await uploadFile(this, localAppearance, remoteAppearance);\n }\n \n // switch the app back\n await this.execute(() => {\n window.location.replace('http://localhost/');\n })\n }\n } else {\n // if browserName is set, reloadSession tries to restart the driver entirely, so unset those\n const newCap: WebdriverIO.Capabilities = _.cloneDeep(\n _.omit(this.requestedCapabilities, ['browserName', 'browserVersion'])\n );\n newCap[OBSIDIAN_CAPABILITY_KEY] = newObsidianOptions;\n\n if (vault) {\n await service.setupVault(newCap);\n await service.electronSetupConfigDir(newCap);\n await this.reloadSession(newCap);\n } else {\n // reload preserving current vault and config dir\n\n // Obsidian debounces saves to the config dir, and so changes to configuration made in the tests may\n // not get saved to disk before the reboot. I haven't found a better way to flush everything than\n // just waiting a bit.\n await sleep(2000);\n\n await this.deleteSession({shutdownDriver: false});\n // while Obsidian is down, modify the vault files to setup plugins and themes\n await service.obsidianLauncher.setupVault({\n vault: oldObsidianOptions.vaultCopy!, copy: false,\n plugins: selectedPlugins, themes: selectedThemes,\n });\n await this.reloadSession(newCap);\n }\n }\n await service.prepareApp();\n };\n return reloadObsidian;\n }\n\n /**\n * Handles vault and sandboxed config directory setup.\n */\n async beforeSession(config: Options.Testrunner, cap: WebdriverIO.Capabilities) {\n try {\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n if (cap[OBSIDIAN_CAPABILITY_KEY].vault != undefined) {\n await this.setupVault(cap);\n }\n if (!isAppium(cap)) {\n await this.electronSetupConfigDir(cap);\n }\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n\n /**\n * Setup custom browser commands.\n */\n async before(cap: WebdriverIO.Capabilities, specs: unknown, browser: WebdriverIO.Browser) {\n this.browser = browser;\n try {\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n\n // You are supposed to add commands via the addCommand hook, however you can't add synchronous methods that\n // way. Also, addCommand completely breaks the stack traces of errors from the methods, while tacking on the\n // methods manually doesn't.\n const newBrowserCommands = {\n ...browserCommands,\n reloadObsidian: this.createReloadObsidian(),\n }\n for (const [name, cmd] of Object.entries(newBrowserCommands)) {\n (browser as any)[name] = cmd;\n }\n\n if (isAppium(browser.requestedCapabilities)) {\n await this.appiumSetContext();\n if (cap[OBSIDIAN_CAPABILITY_KEY].vault) {\n await this.appiumOpenVault();\n }\n }\n await this.prepareApp();\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n\n async after(result: number, cap: WebdriverIO.Capabilities) {\n const browser = this.browser!;\n if (isAppium(cap)) {\n const packageName = await browser.getCurrentPackage();\n await browser.execute('mobile: terminateApp', { appId: packageName });\n // \"mobile: deleteFile\" doesn't work on folders\n // \"mobile:shell\" requires appium --allow-insecure adb_shell\n await browser.execute(\"mobile: shell\", {\n command: \"rm\", args: [\"-rf\", this.androidVaultDir],\n });\n }\n }\n\n /**\n * Cleanup\n */\n async afterSession() {\n for (const tmpDir of this.tmpDirs) {\n await fsAsync.rm(tmpDir, { recursive: true, force: true });\n }\n }\n}\n","import * as path from \"path\"\nimport * as fsAsync from \"fs/promises\"\nimport * as crypto from \"crypto\";\nimport { fileURLToPath } from \"url\";\nimport { TFile } from \"obsidian\";\nimport { OBSIDIAN_CAPABILITY_KEY, NormalizedObsidianCapabilityOptions } from \"../types.js\";\nimport { BasePage } from \"./basePage.js\";\nimport { isAppium } from \"../utils.js\";\nimport _ from \"lodash\";\n\n\n/** Returns true if a vault file path is hidden (either it or one of it's parent directories starts with \".\") */\nfunction isHidden(file: string) {\n return file.split(\"/\").some(p => p.startsWith(\".\"))\n}\n\n/** Returns true if this is a simple text file */\nfunction isText(file: string) {\n return [\".md\", \".json\", \".txt\", \".js\"].includes(path.extname(file).toLocaleLowerCase());\n}\n\n/**\n * Class with various helper methods for writing Obsidian tests using the\n * [page object pattern](https://webdriver.io/docs/pageobjects).\n * \n * You can get an instance of this class either by running\n * ```ts\n * const obsidianPage = await browser.getObsidianPage();\n * ```\n * or just importing it directly with\n * ```ts\n * import { obsidianPage } from \"wdio-obsidian-service\";\n * ```\n * \n * @hideconstructor\n * @category Utilities\n */\nclass ObsidianPage extends BasePage {\n private getObsidianCapabilities(): NormalizedObsidianCapabilityOptions {\n return this.browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];\n }\n\n /**\n * Returns the path to the vault opened in Obsidian.\n * \n * wdio-obsidian-service copies your vault before running tests, so this is the path to the temporary copy.\n */\n getVaultPath(): string {\n const obsidianOptions = this.getObsidianCapabilities();\n if (obsidianOptions.vaultCopy === undefined) {\n throw Error(\"No vault open, set vault in wdio.conf.mts or use reloadObsidian to open a vault dynamically.\")\n }\n return obsidianOptions.uploadedVault ?? obsidianOptions.vaultCopy;\n }\n\n /**\n * Return the Obsidian config dir (just \".obsidian\" unless you changed the config dir name in settings).\n */\n async getConfigDir(): Promise<string> {\n return await this.browser.executeObsidian(({app}) => app.vault.configDir)\n }\n\n /**\n * Returns the Obsidian Platform object. Useful for skipping tests based on whether you are running in desktop or\n * mobile, or based on OS.\n */\n async getPlatform(): Promise<Platform> {\n const obsidianOptions = this.getObsidianCapabilities();\n if (obsidianOptions.vault !== undefined) {\n return await this.browser.executeObsidian(({obsidian}) => {\n const p = obsidian.Platform;\n return {\n isDesktop: p.isDesktop,\n isMobile: p.isMobile,\n isDesktopApp: p.isDesktopApp,\n isMobileApp: p.isMobileApp,\n isIosApp: p.isIosApp,\n isAndroidApp: p.isAndroidApp,\n isPhone: p.isPhone,\n isTablet: p.isTablet,\n isMacOS: p.isMacOS,\n isWin: p.isWin,\n isLinux: p.isLinux,\n isSafari: p.isSafari,\n };\n });\n } else {\n // hack to allow calling getPlatform before opening a vault. This is needed so you can use getPlatform to\n // skip a test before wasting time opening the vault. we don't use this method the rest of the time as you\n // can technically change the size or switch emulation mode during tests\n const appium = isAppium(this.browser.requestedCapabilities);\n const emulateMobile = obsidianOptions.emulateMobile;\n const [width, height] = await this.browser.execute(() => [window.innerWidth, window.innerHeight]);\n\n let isTablet = false;\n let isPhone = false;\n if (appium || emulateMobile) {\n // replicate Obsidian's tablet vs phone breakpoint\n isTablet = (width >= 600 && height >= 600);\n isPhone = !isTablet;\n }\n\n return {\n isDesktop: !(appium || emulateMobile),\n isMobile: appium || emulateMobile,\n isDesktopApp: !appium,\n isMobileApp: appium,\n isIosApp: false, // iOS is not supported\n isAndroidApp: appium,\n isPhone: isPhone,\n isTablet: isTablet,\n isMacOS: !appium && process.platform == 'darwin',\n isWin: !appium && process.platform == 'win32',\n isLinux: !appium && process.platform == 'linux',\n isSafari: false, // iOS is not supported\n };\n }\n }\n\n /**\n * Enables a plugin by ID\n */\n async enablePlugin(pluginId: string): Promise<void> {\n await this.browser.executeObsidian(\n async ({app}, pluginId) => await (app as any).plugins.enablePluginAndSave(pluginId),\n pluginId,\n );\n }\n\n /**\n * Disables a plugin by ID\n */\n async disablePlugin(pluginId: string): Promise<void> {\n await this.browser.executeObsidian(\n async ({app}, pluginId) => await (app as any).plugins.disablePluginAndSave(pluginId),\n pluginId,\n );\n }\n\n /**\n * Sets the theme. Pass \"default\" to reset to the Obsidian theme.\n */\n async setTheme(themeName: string): Promise<void> {\n themeName = themeName == 'default' ? '' : themeName;\n await this.browser.executeObsidian(\n async ({app}, themeName) => await (app as any).customCss.setTheme(themeName),\n themeName,\n )\n }\n\n /**\n * Opens a file in a new tab.\n */\n async openFile(path: string) {\n await this.browser.executeObsidian(async ({app, obsidian}, path) => {\n const file = app.vault.getAbstractFileByPath(path);\n if (file instanceof obsidian.TFile) {\n const leaf = app.workspace.getLeaf('tab');\n await leaf.openFile(file);\n app.workspace.setActiveLeaf(leaf, {focus: true});\n } else {\n throw Error(`No file ${path} exists`);\n }\n }, path)\n }\n\n /**\n * Loads a saved workspace layout from `.obsidian/workspaces.json` by name. Use the core \"Workspaces\"\n * plugin to create the layouts. You can also pass the layout object directly.\n */\n async loadWorkspaceLayout(layout: any): Promise<void> {\n if (typeof layout == \"string\") {\n // read from .obsidian/workspaces.json like the built-in workspaces plugin does\n const workspacesPath = `${await this.getConfigDir()}/workspaces.json`;\n const layoutName = layout;\n try {\n const fileContent = await this.browser.executeObsidian(async ({app}, workspacesPath) => {\n return await app.vault.adapter.read(workspacesPath);\n }, workspacesPath);\n layout = JSON.parse(fileContent)?.workspaces?.[layoutName];\n } catch {\n throw new Error(`Failed to load ${workspacesPath}:${layoutName}`);\n }\n if (!layout) {\n throw new Error(`No workspace ${layoutName} found in ${workspacesPath}`);\n }\n }\n\n await this.browser.executeObsidian(async ({app}, layout) => {\n await app.workspace.changeLayout(layout)\n }, layout)\n }\n\n /**\n * Deletes a file or folder in the vault.\n * @param file path of inside vault, e.g. \"/books/leviathan-wakes.md\"\n */\n async delete(file: string) {\n await this.browser.executeObsidian(async ({app, obsidian}, file, isVaultFile) => {\n file = obsidian.normalizePath(file);\n if (isVaultFile) {\n const fileObj = app.vault.getAbstractFileByPath(file);\n if (fileObj) {\n await app.vault.delete(fileObj, true);\n };\n } else {\n const stat = await app.vault.adapter.stat(file);\n if (stat && stat.type == \"folder\") {\n await app.vault.adapter.rmdir(file, true);\n } else if (stat) {\n await app.vault.adapter.remove(file);\n }\n }\n }, file, !isHidden(file));\n }\n\n /**\n * Writes to a file in the vault. Creates parent directories if needed.\n * @param file path of inside vault, e.g. \"/books/leviathan-wakes.md\"\n * @param content content to write to the file\n */\n async write(file: string, content: string|ArrayBuffer) {\n let strContent: string|undefined, binContent: string|undefined;\n if (typeof content == \"string\") {\n strContent = content;\n } else {\n binContent = Buffer.from(content).toString(\"base64\");\n }\n await this.browser.executeObsidian(async ({app, obsidian}, file, isVaultFile, strContent, binContent) => {\n file = obsidian.normalizePath(file);\n const parent = file.split(\"/\").slice(0, -1).join(\"/\");\n // there's a bug in Obsidian Android where occasionally creating a file silently fails, leaving just an\n // empty file. So retry if that happens. See https://forum.obsidian.md/t/102935\n let success = false;\n let retries = 0;\n while (!success && retries < 8) {\n if (isVaultFile) {\n if (parent && !app.vault.getAbstractFileByPath(parent)) {\n await app.vault.createFolder(parent);\n }\n const fileObj = app.vault.getAbstractFileByPath(file);\n strContent = strContent ?? atob(binContent!);\n if (fileObj) {\n await app.vault.modify(fileObj as TFile, strContent);\n } else {\n await app.vault.create(file, strContent);\n }\n } else {\n await app.vault.adapter.mkdir(parent);\n if (strContent) {\n await app.vault.adapter.write(file, strContent);\n } else {\n const buffer = Uint8Array.from(atob(binContent!), c => c.charCodeAt(0)).buffer;\n await app.vault.adapter.writeBinary(file, buffer);\n }\n }\n\n success = (\n !obsidian.Platform.isAndroidApp ||\n (strContent?.length ?? binContent!.length) === 0 ||\n (await app.vault.adapter.stat(file))!.size > 0\n );\n retries++;\n }\n if (!success) {\n throw new Error(`Failed to write file ${file}`);\n }\n }, file, !isHidden(file) && isText(file), strContent, binContent);\n }\n\n /**\n * Create a folder in the vault. Creates parent directories if needed.\n * @param file path of inside vault, e.g. \"/books\"\n */\n async mkdir(file: string) {\n await this.browser.executeObsidian(async ({app, obsidian}, file, isVaultFile) => {\n file = obsidian.normalizePath(file);\n if (isVaultFile) {\n if (parent && !app.vault.getAbstractFileByPath(file)) {\n await app.vault.createFolder(file);\n }\n } else {\n await app.vault.adapter.mkdir(file);\n }\n }, file, !isHidden(file));\n }\n\n /**\n * Updates the vault by modifying files in place without reloading Obsidian. Can be used to reset the vault back to\n * its original state or to \"switch\" to an entirely different vault without rebooting Obsidian\n * \n * This will only update regular vault files, it won't touch anything under `.obsidian`, and it won't reset any\n * Obsidian config or plugin settings. But if all you need is to reset the vault files, this can be used as a faster\n * alternative to {@link ObsidianBrowserCommands.reloadObsidian | reloadObsidian}.\n * \n * You'll often want to combine resetVault with something like this to reset your plugin's configuration as well:\n * ```ts\n * await browser.executeObsidian(async ({plugins}, settings) => {\n * Object.assign(plugins.myPlugin.settings, settings);\n * await plugins.myPlugin.saveSettings();\n * }, {...});\n * ```\n *\n * If no vault is passed, it resets the vault back to the oringal vault opened by the tests. You can also pass a\n * path to a different vault, and it will replace the current files with the files of that vault (similar to an\n * \"rsync\"). Or, instead of passing a vault path you can pass an object mapping vault file paths to file content.\n * E.g.\n * ```ts\n * obsidianPage.resetVault({\n * 'path/in/vault.md': \"Hello World\",\n * })\n * ```\n * \n * You can also pass multiple vaults and objects, and they will be merged. This can be useful if you want to add a\n * few small modifications to the base vault. e.g:\n * ```ts\n * obsidianPage.resetVault('./path/to/vault', {\n * \"books/leviathan-wakes.md\": \"...\",\n * })\n * ```\n */\n async resetVault(...vaults: (string|Record<string, string|ArrayBuffer>)[]) {\n const obsidianOptions = this.getObsidianCapabilities();\n if (!obsidianOptions.vault) {\n // open an empty vault if there's no vault open\n const defaultVaultPath = path.resolve(fileURLToPath(import.meta.url), '../../default-vault');\n await this.browser.reloadObsidian({vault: defaultVaultPath});\n }\n const configDir = await this.getConfigDir();\n vaults = vaults.length == 0 ? [obsidianOptions.vault!] : vaults;\n\n // list all files in the new vault\n type NewFileInfo = {type: \"file\"| \"folder\", sourceContent?: string|ArrayBuffer, sourceFile?: string};\n const newVault: Map<string, NewFileInfo> = new Map();\n for (let vault of vaults) {\n if (typeof vault == \"string\") {\n vault = path.resolve(vault);\n const files = await fsAsync.readdir(vault, { recursive: true, withFileTypes: true });\n for (const f of files) {\n const fullPath = path.join(f.parentPath, f.name);\n const vaultPath = path.relative(vault, fullPath).split(path.sep).join(\"/\");\n if (!vaultPath.startsWith(configDir + \"/\")) {\n if (f.isDirectory()) {\n newVault.set(vaultPath, {type: 'folder'});\n } else {\n newVault.set(vaultPath, {type: 'file', sourceFile: fullPath});\n }\n }\n }\n } else {\n for (const [file, content] of Object.entries(vault)) {\n newVault.set(file, {type: \"file\", sourceContent: content});\n }\n const folders = new Set(Object.keys(vault).map(p => path.posix.dirname(p)).filter(p => p !== \".\"));\n for (const folder of folders) {\n newVault.set(folder, {type: \"folder\"});\n }\n }\n }\n\n // list all files in the current vault\n type CurrFileInfo = {type: \"file\"| \"folder\", hash?: string};\n const currVault = new Map(await this.browser.executeObsidian(({app}, configDir) => {\n async function hash(data: ArrayBuffer) {\n const hashBuffer = await window.crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n }\n\n async function listRecursive(path: string): Promise<[string, CurrFileInfo][]> {\n const result: [string, CurrFileInfo][] = [];\n const { folders, files } = await app.vault.adapter.list(path);\n for (const folder of folders) {\n if (!folder.startsWith(configDir + \"/\")) {\n result.push([folder, {type: \"folder\"}]);\n result.push(...await listRecursive(folder));\n }\n }\n for (const file of files) {\n if (!file.startsWith(configDir + \"/\")) {\n let fileHash = (app.metadataCache as any).getFileInfo(file)?.hash;\n if (!fileHash) { // hidden files etc. aren't in Obsidian's metadata cache\n fileHash = await hash(await app.vault.adapter.readBinary(file));\n }\n result.push([file, { type: \"file\", hash: fileHash }]);\n }\n }\n return result;\n }\n\n return listRecursive(\"/\");\n }, configDir));\n\n // delete any files that need to be deleted\n for (const file of [...currVault.keys()].sort().reverse()) {\n const currFileInfo = currVault.get(file)!\n if (!newVault.has(file) || newVault.get(file)!.type != currFileInfo.type) {\n await this.delete(file);\n }\n }\n\n // create files and folders\n for (const [file, newFileInfo] of _.sortBy([...newVault.entries()], 0)) {\n const currFileInfo = currVault.get(file);\n if (newFileInfo.type == \"file\") {\n let content = newFileInfo.sourceContent;\n if (!content) {\n content = (await fsAsync.readFile(newFileInfo.sourceFile!)).buffer as ArrayBuffer;\n }\n const hash = crypto.createHash(\"SHA256\")\n .update(typeof content == \"string\" ? content : new Uint8Array(content))\n .digest(\"hex\");\n if (!currFileInfo || currFileInfo.hash != hash) {\n await this.write(file, content);\n }\n } else if (newFileInfo.type == \"folder\" && !currFileInfo) {\n await this.mkdir(file);\n }\n }\n }\n}\n\n/**\n * Info on the platform we are running on or emulating, in similar format as\n * [obsidian.Platform](https://docs.obsidian.md/Reference/TypeScript+API/Platform)\n * @category Types\n */\nexport interface Platform {\n /**\n * The UI is in desktop mode.\n */\n isDesktop: boolean;\n /**\n * The UI is in mobile mode.\n */\n isMobile: boolean;\n /**\n * We're running the Electron-based desktop app.\n * Note, when running under `emulateMobile` this will still be true and isDesktop will be false.\n */\n isDesktopApp: boolean;\n /**\n * We're running the Capacitor-js mobile app.\n * Note, when running under `emulateMobile` this will still be false and isMobile will be true.\n */\n isMobileApp: boolean;\n /** \n * We're running the iOS app.\n * Note, wdio-obsidian-service doesn't support iOS yet, so this will always be false.\n */\n isIosApp: boolean;\n /**\n * We're running the Android app.\n */\n isAndroidApp: boolean;\n /**\n * We're in a mobile app that has very limited screen space.\n */\n isPhone: boolean;\n /**\n * We're in a mobile app that has sufficiently large screen space.\n */\n isTablet: boolean;\n /**\n * We're on a macOS device, or a device that pretends to be one (like iPhones and iPads).\n * Typically used to detect whether to use command-based hotkeys vs ctrl-based hotkeys.\n */\n isMacOS: boolean;\n /**\n * We're on a Windows device.\n */\n isWin: boolean;\n /**\n * We're on a Linux device.\n */\n isLinux: boolean;\n /**\n * We're running in Safari.\n * Note, wdio-obsidian-service doesn't support iOS yet, so this will always be false.\n */\n isSafari: boolean;\n};\n\n/**\n * Instance of {@link ObsidianPage} with helper methods for writing Obsidian tests.\n * @category Utilities\n */\nconst obsidianPage = new ObsidianPage()\nexport default obsidianPage;\nexport { ObsidianPage };\n","import type { ObsidianBrowserCommands } from \"./browserCommands.js\";\nimport type { PluginEntry, ThemeEntry, DownloadedPluginEntry, DownloadedThemeEntry } from \"obsidian-launcher\";\n\nexport const OBSIDIAN_CAPABILITY_KEY = \"wdio:obsidianOptions\";\n\n/**\n * Options passed to an \"wdio:obsidianOptions\" capability in wdio.conf.mts. E.g.\n * ```ts\n * // ...\n * capabilities: [{\n * browserName: \"obsidian\",\n * browserVersion: \"latest\",\n * 'wdio:obsidianOptions': {\n * installerVersion: 'earliest',\n * plugins: [\".\"],\n * },\n * }],\n * ```\n * \n * @category Options\n */\nexport interface ObsidianCapabilityOptions {\n /**\n * Version of Obsidian to download and run.\n * \n * Can be set to a specific version or one of:\n * - \"latest\": Run the latest non-beta Obsidian version\n * - \"latest-beta\": Run the latest beta Obsidian version (or latest is there is no current beta)\n * - To download Obsidian beta versions you'll need to have an Obsidian Insiders account and either set the \n * `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` env vars (`.env` file is supported) or pre-download the Obsidian \n * beta version with `npx obsidian-launcher download -v latest-beta`\n * - \"earliest\": Run the `minAppVersion` set in your `manifest.json`\n * \n * Defaults to \"latest\".\n * \n * You can also use the wdio capability `browserVersion` field to set the Obsidian version.\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n */\n appVersion?: string\n\n /**\n * Version of the Obsidian installer to download and run.\n * \n * Obsidian is Desktop distributed in two parts, the app which contains the JS, and the installer which is the\n * binary with Electron. Obsidian's auto update only updates the app, so users on the same Obsidian version can be\n * running different Electron versions. You can use this to test your plugin against different installer/Electron\n * versions.\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n *\n * Can be set to a specific version string or one of:\n * - \"latest\": Run the latest Obsidian installer compatible with `appVersion`.\n * - \"earliest\": Run the oldest Obsidian installer compatible with `appVersion`.\n * \n * Defaults to \"earliest\".\n */\n installerVersion?: string,\n\n /**\n * List of plugins to install.\n * \n * Each entry is a path to the local plugin to install, e.g. [\".\"] or [\"dist\"] depending on your build setup. Paths\n * are relative to your `wdio.conf.mts`. You can also pass objects. If you pass an object it should contain one of\n * `path` (to install a local plugin), `repo` (to install a plugin from GitHub), or `id` (to install a community\n * plugin). You can set `enabled: false` to install the plugin but start it disabled. You can enable the plugin\n * later using {@link ObsidianBrowserCommands.reloadObsidian|browser.reloadObsidian} or\n * {@link ObsidianPage.enablePlugin}.\n */\n plugins?: PluginEntry[],\n\n /**\n * List of themes to install.\n * \n * Each entry is a path to the local theme to install. Paths are relative to your `wdio.conf.mts`. You can also pass\n * an object. If you pass an object it should contain one of `path` (to install a local theme), `repo` (to install a\n * theme from GitHub), or `name` (to install a community theme). You can set `enabled: false` to install the theme,\n * but start it disabled. You can only have one enabled theme, so if you pass multiple you'll have to disable all\n * but one.\n */\n themes?: ThemeEntry[],\n\n /**\n * The path to the vault to open.\n * \n * The vault will be copied, so any changes made in your tests won't affect the original. If omitted, no vault will\n * be opened and you'll need to call {@link ObsidianBrowserCommands.reloadObsidian|browser.reloadObsidian} to open a\n * vault during your tests. Path is relative to your `wdio.conf.mts`.\n */\n vault?: string,\n\n /**\n * Set to true to emulate mobile on the Electron desktop app. This uses Obsidian `app.emulateMobile()` to switch\n * Obsidian to the mobile UI, and you can use Chrome's mobileEmulation to set the screen size. You can compare\n * tablet vs phone UIs by setting the screen size or emulated device. Obsidian tablet UI triggers at\n * width/height >= 600.\n *\n * Note that Obsidian Mobile runs on Capacitor instead of Electron so there are various platform differences that\n * can't be emulated. But it's good enough for most cases as long as you aren't interacting directly with the\n * operating system or Electron APIs. You can use an Android Virtual Device instead if you want a more accurate\n * (but slower) mobile test.\n *\n * See [Mobile Emulation](../README.md#mobile-emulation) for more info.\n * See [Android](../README.md#android) if you want to test the real mobile app on an Android Virtual Device instead.\n */\n emulateMobile?: boolean,\n\n /**\n * Path to the Obsidian binary to use. If omitted it will be downloaded automatically.\n */\n binaryPath?: string,\n\n /**\n * Path to the app asar to load into obsidian. If omitted it will be downloaded automatically.\n */\n appPath?: string,\n}\n\n\n/** Internal type, capability options after being normalized by onPrepare */\nexport interface NormalizedObsidianCapabilityOptions {\n appVersion: string\n installerVersion: string,\n plugins: DownloadedPluginEntry[],\n themes: DownloadedThemeEntry[],\n vault?: string,\n vaultCopy?: string,\n /** Path off the vault on the appium device */\n uploadedVault?: string,\n emulateMobile: boolean,\n binaryPath?: string,\n appPath?: string,\n}\n\n\n/**\n * Options passed to wdio-obsidian-service service in wdio.conf.mts. E.g.\n * ```js\n * // ...\n * services: [[\"obsidian\", {versionsUrl: \"file:///path/to/obsidian-versions.json\"}]]\n * ```\n * You'll usually want to leave these options as the default, they are mostly useful for wdio-obsidian-service's\n * internal tests.\n * \n * @category Options\n */\nexport interface ObsidianServiceOptions {\n /**\n * Override the `obsidian-versions.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/jesse-r-s-hines/wdio-obsidian-service/HEAD/obsidian-versions.json\n * which is auto-updated to contain information on available Obsidian versions.\n */\n versionsUrl?: string,\n /**\n * Override the `community-plugins.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-plugins.json\n */\n communityPluginsUrl?: string,\n /**\n * Override the `community-css-themes.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-css-themes.json\n */\n communityThemesUrl?: string,\n}\n\ndeclare global {\n namespace WebdriverIO {\n interface Capabilities {\n [OBSIDIAN_CAPABILITY_KEY]?: ObsidianCapabilityOptions,\n }\n\n interface Browser extends ObsidianBrowserCommands {}\n }\n}\n","// src/index.ts\nvar globals = globalThis._wdioGlobals = globalThis._wdioGlobals || /* @__PURE__ */ new Map();\nvar GLOBALS_ERROR_MESSAGE = `No browser instance registered. Don't import @wdio/globals outside of the WDIO testrunner context. Or you have two two different \"@wdio/globals\" packages installed.`;\nfunction proxyHandler(key) {\n return {\n get: (self, prop) => {\n if (!globals.has(key)) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n const receiver = globals.get(key);\n const field = receiver[prop];\n return typeof field === \"function\" ? field.bind(receiver) : field;\n }\n };\n}\nvar browser = new Proxy(\n class Browser {\n },\n proxyHandler(\"browser\")\n);\nvar driver = new Proxy(\n class Browser2 {\n },\n proxyHandler(\"driver\")\n);\nvar multiremotebrowser = new Proxy(\n class Browser3 {\n },\n proxyHandler(\"multiremotebrowser\")\n);\nvar $ = (...args) => {\n if (!globals.has(\"$\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"$\")(...args);\n};\nvar $$ = (...args) => {\n if (!globals.has(\"$$\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"$$\")(...args);\n};\nvar expect = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\")(...args);\n};\nvar ASYNC_MATCHERS = [\n \"any\",\n \"anything\",\n \"arrayContaining\",\n \"objectContaining\",\n \"stringContaining\",\n \"stringMatching\"\n];\nfor (const matcher of ASYNC_MATCHERS) {\n expect[matcher] = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\")[matcher](...args);\n };\n}\nexpect.not = ASYNC_MATCHERS.reduce((acc, matcher) => {\n acc[matcher] = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\").not[matcher](...args);\n };\n return acc;\n}, {});\nexpect.extend = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n const expect2 = globals.get(\"expect\");\n return expect2.extend(...args);\n};\nfunction _setGlobal(key, value, setGlobal = true) {\n globals.set(key, value);\n if (setGlobal) {\n globalThis[key] = value;\n }\n}\nexport {\n $,\n $$,\n _setGlobal,\n browser,\n driver,\n expect,\n multiremotebrowser\n};\n","import * as wdioGlobals from \"@wdio/globals\"\n\n/**\n * Base page object for use in the wdio [page object pattern](https://webdriver.io/docs/pageobjects).\n * \n * You can pass the browser to the page object, which allows using the object even in wdio standalone mode.\n */\nexport class BasePage {\n private _browser: WebdriverIO.Browser|undefined\n constructor(browser?: WebdriverIO.Browser) {\n this._browser = browser;\n }\n\n /**\n * Returns the browser instance.\n * @hidden\n */\n protected get browser(): WebdriverIO.Browser {\n return this._browser ?? wdioGlobals.browser;\n }\n}\n","import path from \"path\";\nimport crypto from \"crypto\";\nimport fsAsync from \"fs/promises\";\nimport _ from \"lodash\";\n\nexport async function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/** Quote string for use in shell scripts */\nexport function quote(input: string) {\n return `'${input.replace(/'/g, \"'\\\\''\")}'`;\n}\n\nexport async function fileExists(path: string) {\n try {\n await fsAsync.access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Gets the appium options.\n * Handles combining `\"appium:foo\"` and `\"appium:options\": {\"foo\": ...}` style options.\n */\nexport function getAppiumOptions(\n cap: WebdriverIO.Capabilities,\n): Exclude<WebdriverIO.Capabilities['appium:options'], undefined> {\n let result: any = _.pickBy(cap, (v, k) => k.startsWith(\"appium:\") && k != 'appium:options');\n result = _.mapKeys(result, (v, k) => k.slice(7))\n return {...result, ...cap['appium:options']};\n}\n\n/** Returns true if this capability is for Appium */\nexport function isAppium(cap: WebdriverIO.Capabilities) {\n return getAppiumOptions(cap).automationName?.toLocaleLowerCase() === 'uiautomator2';\n}\n\n/**\n * Push a folder to the appium device.\n */\nexport async function uploadFolder(browser: WebdriverIO.Browser, src: string, dest: string) {\n src = path.resolve(src);\n dest = path.posix.normalize(dest).replace(/\\/$/, '');\n\n let files = await fsAsync.readdir(src, {recursive: true, withFileTypes: true});\n files = _.sortBy(files, f => path.join(f.parentPath, f.name)); // sort files before children\n\n await browser.execute(\"mobile: shell\", {command: \"mkdir\", args: [\"-p\", quote(dest)]});\n for (const file of files) {\n const srcPath = path.join(file.parentPath, file.name);\n const relPath = path.relative(src, path.join(file.parentPath, file.name));\n const destPath = path.posix.join(dest, relPath.split(path.sep).join(\"/\"));\n if (file.isDirectory()) {\n await browser.execute(\"mobile: shell\", {command: \"mkdir\", args: [\"-p\", quote(destPath)]});\n } else if (file.isFile()) {\n await uploadFile(browser, srcPath, destPath);\n }\n }\n}\n\n\n/**\n * Uploads a file to the appium device.\n * Wrapper around pushFile that works around a bug in pushFile where it doesn't escape special characters in parent\n * directory names. See https://github.com/appium/appium-android-driver/issues/1004\n */\nexport async function uploadFile(browser: WebdriverIO.Browser, src: string, dest: string) {\n src = path.resolve(src);\n dest = path.posix.normalize(dest);\n const slug = crypto.randomBytes(8).toString(\"base64url\").replace(/[-_]/g, '0');\n const tmpFile = `/data/local/tmp/upload-${slug}.tmp`;\n const content = await fsAsync.readFile(src);\n\n await browser.pushFile(tmpFile, content.toString('base64'));\n await browser.execute(\"mobile: shell\", {command: \"mv\", args: [tmpFile, quote(dest)]});\n}\n\n/**\n * Downloads a file from the appium device.\n */\nexport async function downloadFile(browser: WebdriverIO.Browser, src: string, dest: string) {\n src = path.posix.normalize(src);\n dest = path.resolve(dest);\n const content = Buffer.from(await browser.pullFile(src), \"base64\");\n await fsAsync.writeFile(dest, content);\n}\n","import type * as obsidian from \"obsidian\"\nimport { ObsidianPage } from \"./pageobjects/obsidianPage.js\"\nimport { NormalizedObsidianCapabilityOptions, OBSIDIAN_CAPABILITY_KEY } from \"./types.js\";\n\nexport const browserCommands = {\n /**\n * Wrapper around browser.execute that passes the Obsidian API to the function. The first argument to the function\n * is an object containing keys:\n * - app: Obsidian app instance\n * - obsidian: Full Obsidian API\n * - plugins: Object of all installed plugins, mapped by plugin id converted to camelCase.\n * - require: The customized require function Obsidian makes available to plugins. This is also available globally,\n * so you can just use `require` directly instead of from {@link ExecuteObsidianArg} if you prefer.\n * \n * Like `brower.execute`, you can pass other extra arguments to the function.\n * \n * See also: https://webdriver.io/docs/api/browser/execute\n * \n * Example usage\n * ```ts\n * const file = browser.executeObsidian(({app, obsidian}, path) => {\n * return app.vault.getMarkdownFiles().find(f => f.path == path);\n * })\n * ```\n * \n * Note: The same caveats as `browser.execute` apply. The function will be stringified and then run inside Obsidian,\n * so you can't capture any local variables. E.g.\n * \n * This *won't* work:\n * ```ts\n * import { FileView } from \"obsidian\"\n * browser.executeObsidian(({app}) => {\n * if (leaf.view instanceof FileView) {\n * ...\n * }\n * })\n * ```\n * do this instead:\n * ```ts\n * browser.executeObsidian(({app, obsidian}) => {\n * if (leaf.view instanceof obsidian.FileView) {\n * ...\n * }\n * })\n * ```\n */\n async executeObsidian<Return, Params extends unknown[]>(\n this: WebdriverIO.Browser,\n func: (obs: ExecuteObsidianArg, ...params: Params) => Return,\n ...params: Params\n ): Promise<Return> {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n if (obsidianOptions.vault === undefined) {\n throw Error(\"No vault open, set vault in wdio.conf.mts or use reloadObsidian to open a vault dynamically.\")\n }\n // Call the function. Just stringify the function (browser.execute stringifies it anyways). Add a workaround for\n // node fs errors returning useless error messages. If the thrown error has a \"code\" field that is a string, the\n // chromedriver throws an error like this with no further info:\n // \"unknown error: call function result missing int 'status'\"\n // I think this is a bug in ChromeDevtoolsProtocol or chromedriver. See\n // https://github.com/electron-userland/spectron/issues/1057\n const result = await this.execute<Return, Params>(`\n const require = window.wdioObsidianService().require;\n try {\n return await (\n ${func.toString()}\n ).call(null, window.wdioObsidianService(), ...arguments);\n } catch (e) {\n if (\"code\" in e && typeof e.code != \"number\") {\n delete e.code;\n }\n throw e;\n }\n `, ...params);\n // TODO Should maybe add in the TransformReturn and TransformElement bit that wdio has recently added to the\n // execute types, though it causes weird affects if `func` returns type any.\n return result as Return;\n },\n\n /**\n * Executes an Obsidian command by id.\n * @param id Id of the command to run.\n */\n async executeObsidianCommand(this: WebdriverIO.Browser, id: string) {\n const result = await this.executeObsidian(({app}, id) => (app as any).commands.executeCommandById(id), id);\n if (!result) {\n throw Error(`Obsidian command ${id} not found or failed.`);\n }\n },\n\n /**\n * Returns the Obsidian app version this test is running under.\n */\n getObsidianVersion(this: WebdriverIO.Browser): string {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n return obsidianOptions.appVersion;\n },\n \n /**\n * Returns the Obsidian installer version this test is running under.\n */\n getObsidianInstallerVersion(this: WebdriverIO.Browser): string {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n return obsidianOptions.installerVersion;\n },\n\n /**\n * Returns the ObsidianPage object with convenience helper functions.\n * You can also just import the page object directly with\n * ```ts\n * import { obsidianPage } from \"wdio-obsidian-service\"\n * ```\n */\n getObsidianPage(this: WebdriverIO.Browser): ObsidianPage {\n return new ObsidianPage(this);\n },\n}\n\n/**\n * Extra commands added to the WDIO Browser instance.\n * \n * See also: https://webdriver.io/docs/api/browser\n * @interface\n * @category Utilities\n */\nexport type ObsidianBrowserCommands = typeof browserCommands & {\n /**\n * Relaunch obsidian. Can be used to switch to a new vault, change the plugin list, or just to reboot Obsidian.\n * \n * As this does a full reboot of Obsidian, this is rather slow. In many cases you can use\n * {@link ObsidianPage.resetVault} instead, which modifies vault files in place without rebooting Obsidian. If all\n * your tests use the same vault, you can also just set the vault in the `wdio.conf.mts` capabilities section.\n * \n * @param params.vault Path to the vault to open. The vault will be copied, so any changes made in your tests won't\n * be persited to the original. If omitted, it will reboot Obsidian with the current vault without creating a\n * new copy of the vault.\n * @param params.plugins List of plugin ids to enable. If omitted it will keep current plugin list. Note, all the\n * plugins must be defined in your wdio.conf.mts capabilities. You can also use the enablePlugin and \n * disablePlugin commands to change plugins without relaunching Obsidian.\n * @param params.theme Name of the theme to enable. If omitted it will keep the current theme. Pass \"default\" to\n * switch back to the default theme. Like with plugins, the theme must be defined in wdio.conf.mts.\n */\n reloadObsidian(params?: {\n vault?: string,\n plugins?: string[], theme?: string,\n }): Promise<void>;\n // This command is implemented in the service hooks.\n};\n\n/**\n * Argument passed to the {@link ObsidianBrowserCommands.executeObsidian | executeObsidian} browser command.\n * @category Types\n */\nexport interface ExecuteObsidianArg {\n /**\n * There is a global \"app\" instance, but that may be removed in the future so you can use this to access it from\n * tests. See https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines#Avoid+using+global+app+instance\n */\n app: obsidian.App,\n\n /**\n * The full obsidian API. See https://github.com/obsidianmd/obsidian-api/blob/master/obsidian.d.ts\n */\n obsidian: typeof obsidian,\n\n /**\n * Object containing all installed plugins mapped by their id. Plugin ids are converted to camelCase for ease of\n * destructuring.\n * \n * If you want to add typings for your plugin(s) you can use something like this in a `.d.ts`:\n * ```ts\n * import type MyPlugin from \"../src/main.js\"\n * declare module \"wdio-obsidian-service\" {\n * interface InstalledPlugins {\n * myPlugin: MyPlugin,\n * }\n * }\n * ```\n */\n plugins: InstalledPlugins,\n\n /**\n * The customized require function Obsidian makes available to plugins. This is also available globally, so you can\n * just use `require` directly instead of from {@link ExecuteObsidianArg} if you prefer.\n */\n require: NodeJS.Require,\n}\n\n/**\n * Installed plugins, mapped by their id converted to camelCase\n * @category Types\n */\nexport interface InstalledPlugins extends Record<string, obsidian.Plugin> {\n}\n","import { remote } from 'webdriverio'\nimport type { Capabilities, Options } from '@wdio/types'\nimport { ObsidianLauncherService, ObsidianWorkerService } from \"./service.js\"\nimport { ObsidianServiceOptions } from \"./types.js\"\n\n/**\n * Starts an Obsidian instance for WDIO standalone mode.\n * \n * For testing, you'll usually want to use the WDIO testrunner with Mocha, wdio.conf.mts, etc. to launch WDIO. However\n * if you want to use WDIO for some kind of scripting scenario, you can use this function to launch a WDIO standalone\n * session connected to Obsidian.\n * \n * See also: https://webdriver.io/docs/setuptypes/#standalone-mode\n * \n * @category WDIO Helpers\n */\nexport async function startWdioSession(\n params: Capabilities.WebdriverIOConfig,\n serviceOptions?: ObsidianServiceOptions,\n): Promise<WebdriverIO.Browser> {\n serviceOptions = serviceOptions ?? {};\n const capabilities = params.capabilities as WebdriverIO.Capabilities;\n const testRunnerOptions: Options.Testrunner = {\n cacheDir: params.cacheDir,\n };\n const launcherService = new ObsidianLauncherService(serviceOptions, [capabilities] as any, testRunnerOptions);\n const workerService = new ObsidianWorkerService(serviceOptions, capabilities, testRunnerOptions);\n\n await launcherService.onPrepare(testRunnerOptions, [capabilities]);\n await workerService.beforeSession(testRunnerOptions, capabilities);\n\n const browser = await remote(params);\n\n await workerService.before(capabilities, [], browser);\n\n return browser;\n}\n"],"mappings":"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","names":["ObsidianLauncher","fsAsync","path","fileURLToPath","path","fsAsync","crypto","browser","resolve","path","browser","_","pluginId","themeName","path","workspacesPath","layout","file","strContent","binContent","parent","configDir","id","_","path","fileURLToPath","browser","androidVault","width","height","resolve","remote","fsAsync","browser","launcher","ObsidianLauncher"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/service.ts","../src/pageobjects/obsidianPage.ts","../src/types.ts","../../../node_modules/.pnpm/@wdio+globals@9.17.0_expect-webdriverio@5.4.1_webdriverio@9.18.4/node_modules/@wdio/globals/build/index.js","../src/pageobjects/basePage.ts","../src/utils.ts","../src/browserCommands.ts","../src/standalone.ts"],"sourcesContent":["/**\n * @module\n * @document ../README.md\n * @categoryDescription Options\n * Capability and service options.\n * @categoryDescription WDIO Helpers\n * Helpers for use in wdio.conf.mts, or for launching WDIO in standalone mode.\n * @categoryDescription Utilities\n * Browser commands and helper functions for writing tests.\n */\nimport ObsidianLauncher from \"obsidian-launcher\";\nimport { deprecate } from \"util\";\n\nimport { ObsidianLauncherService, ObsidianWorkerService } from \"./service.js\";\n/** @hidden */\nexport default ObsidianWorkerService;\n/** @hidden */\nexport const launcher = ObsidianLauncherService;\n\nexport type { ObsidianCapabilityOptions, ObsidianServiceOptions } from \"./types.js\";\nexport type { ObsidianBrowserCommands, ExecuteObsidianArg, InstalledPlugins } from \"./browserCommands.js\";\nexport { default as obsidianPage } from \"./pageobjects/obsidianPage.js\";\nexport type { ObsidianPage, Platform } from \"./pageobjects/obsidianPage.js\";\nexport type { PluginEntry, ThemeEntry } from \"obsidian-launcher\";\n\nexport { minSupportedObsidianVersion } from \"./service.js\";\nexport { startWdioSession } from \"./standalone.js\";\n\n// Some convenience helpers for use in wdio.conf.mts\n\n/**\n * Returns true if there is a current Obsidian beta and we have the credentials to download it, or its already in cache.\n * @category WDIO Helpers\n * @param opts.cacheDir Obsidian cache dir, defaults to `.obsidian-cache`.\n */\nexport async function obsidianBetaAvailable(opts: string|{cacheDir?: string} = {}) {\n opts = typeof opts == \"string\" ? {cacheDir: opts} : opts;\n const launcher = new ObsidianLauncher(opts);\n const versionInfo = await launcher.getVersionInfo(\"latest-beta\");\n return versionInfo.isBeta && await launcher.isAvailable(versionInfo.version);\n}\n\n/**\n * Resolves Obsidian app and installer version strings to absolute versions.\n * \n * @category WDIO Helpers\n * @deprecated Use parseObsidianVersions instead\n */\nexport const resolveObsidianVersions = deprecate(async function(\n appVersion: string, installerVersion: string, cacheDir?: string,\n): Promise<[string, string]> {\n const launcher = new ObsidianLauncher({cacheDir: cacheDir});\n return await launcher.resolveVersion(appVersion, installerVersion);\n}, 'resolveObsidianVersions is deprecated, use parseObsidianVersions instead');\n\n\n/**\n * Parses a string of Obsidian versions into [appVersion, installerVersion] tuples. This is a convenience helper for use\n * in `wdio.conf.mts`\n * \n * `versions` should be a space separated list of Obsidian app versions. You can optionally specify the installer\n * version by using \"appVersion/installerVersion\" e.g. `\"1.7.7/1.8.10\"`.\n * \n * Example: \n * ```js\n * parseObsidianVersions(\"1.8.10/1.7.7 latest latest-beta/earliest\")\n * ```\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n * \n * @category WDIO Helpers\n * @param versions string to parse\n * @param opts.cacheDir Obsidian cache dir, defaults to `.obsidian-cache`.\n * @returns [appVersion, installerVersion][] resolved to specific versions.\n */\nexport async function parseObsidianVersions(\n versions: string,\n opts: {cacheDir?: string} = {},\n): Promise<[string, string][]> {\n const launcher = new ObsidianLauncher({cacheDir: opts.cacheDir});\n return launcher.parseVersions(versions);\n}\n","import fs from \"fs\"\nimport fsAsync from \"fs/promises\"\nimport path from \"path\"\nimport { SevereServiceError } from 'webdriverio'\nimport type { Capabilities, Options, Services } from '@wdio/types'\nimport logger from '@wdio/logger'\nimport { fileURLToPath } from \"url\"\nimport ObsidianLauncher, {\n PluginEntry, ThemeEntry, DownloadedPluginEntry, DownloadedThemeEntry,\n} from \"obsidian-launcher\"\nimport { browserCommands } from \"./browserCommands.js\"\nimport {\n ObsidianServiceOptions, NormalizedObsidianCapabilityOptions, OBSIDIAN_CAPABILITY_KEY,\n} from \"./types.js\"\nimport {\n isAppium, appiumUploadFiles, appiumDownloadFile, getAppiumOptions, fileExists,\n} from \"./utils.js\";\nimport semver from \"semver\"\nimport _ from \"lodash\"\n\n\nconst log = logger(\"wdio-obsidian-service\");\n\nfunction getDefaultCacheDir(rootDir: string) {\n return path.resolve(rootDir, process.env.WEBDRIVER_CACHE_DIR ?? process.env.OBSIDIAN_CACHE ?? \"./.obsidian-cache\");\n}\n\n/** By default wdio continues on service errors, so we throw a SevereServiceError to make it bail on error */\nfunction getServiceErrorMessage(e: any) {\n return (\n `Failed to download and setup Obsidian. Caused by:\\n` +\n `${e.stack}\\n`+\n ` ------The above causes:-----`\n );\n}\n\nfunction resolveEntry(rootDir: string, entry: PluginEntry|ThemeEntry): PluginEntry|ThemeEntry {\n if (typeof entry == \"string\") {\n return path.resolve(rootDir, entry);\n } else if ('path' in entry) {\n return {...entry, path: path.resolve(rootDir, entry.path)};\n } else {\n return entry;\n }\n}\n\n/** Returns a plugin list with only the selected plugin ids enabled. */\nfunction selectPlugins(currentPlugins: DownloadedPluginEntry[], selection?: string[]): DownloadedPluginEntry[] {\n if (selection !== undefined) {\n const unknownPlugins = _.difference(selection, currentPlugins.map(p => p.id));\n if (unknownPlugins.length > 0) {\n throw Error(`Unknown plugin ids: ${unknownPlugins.join(', ')}`)\n }\n return currentPlugins.map(p => ({\n ...p,\n enabled: selection.includes(p.id) || p.id == \"wdio-obsidian-service-plugin\",\n }));\n } else {\n return currentPlugins;\n }\n}\n\n/** Returns a theme list with only the selected theme enabled. */\nfunction selectThemes(currentThemes: DownloadedThemeEntry[], selection?: string): DownloadedThemeEntry[] {\n if (selection !== undefined) {\n if (selection != \"default\" && currentThemes.every(t => t.name != selection)) {\n throw Error(`Unknown theme: ${selection}`);\n }\n return currentThemes.map(t => ({...t, enabled: selection != 'default' && t.name === selection}));\n } else {\n return currentThemes;\n }\n}\n\nfunction getNormalizedObsidianOptions(cap: WebdriverIO.Capabilities): NormalizedObsidianCapabilityOptions {\n return cap[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n}\n\n\n/**\n * Minimum Obsidian version that wdio-obsidian-service supports.\n * @category WDIO Helpers\n */\nexport const minSupportedObsidianVersion: string = \"1.0.3\"\n\n\n/**\n * wdio launcher service.\n * Use in wdio.conf.mts like so:\n * ```ts\n * services: ['obsidian'],\n * ```\n * @hidden\n */\nexport class ObsidianLauncherService implements Services.ServiceInstance {\n private obsidianLauncher: ObsidianLauncher\n private readonly helperPluginPath: string\n private readonly rootDir: string\n\n constructor (\n public options: ObsidianServiceOptions,\n public capabilities: WebdriverIO.Capabilities,\n public config: Options.Testrunner\n ) {\n this.rootDir = config.rootDir || process.cwd();\n this.obsidianLauncher = new ObsidianLauncher({\n cacheDir: config.cacheDir ?? getDefaultCacheDir(this.rootDir),\n versionsUrl: options.versionsUrl,\n communityPluginsUrl: options.communityPluginsUrl, communityThemesUrl: options.communityThemesUrl,\n });\n this.helperPluginPath = path.resolve(fileURLToPath(import.meta.url), '../../helper-plugin');\n }\n\n /**\n * Validates wdio:obsidianOptions and downloads Obsidian, plugins, and themes.\n */\n async onPrepare(config: Options.Testrunner, capabilities: Capabilities.TestrunnerCapabilities) {\n try {\n if (!Array.isArray(capabilities)) {\n capabilities = Object.values(capabilities).map(\n (multiremoteOption) => (multiremoteOption as Capabilities.WithRequestedCapabilities).capabilities,\n );\n }\n\n const obsidianCapabilities = capabilities.flatMap((cap) => {\n if ((\"browserName\" in cap) && cap.browserName === \"obsidian\") {\n return [cap as WebdriverIO.Capabilities];\n } else {\n return [];\n }\n });\n\n for (const cap of obsidianCapabilities) {\n const obsidianOptions = cap[OBSIDIAN_CAPABILITY_KEY] ?? {};\n \n // check vault\n const vault = obsidianOptions.vault ? path.resolve(this.rootDir, obsidianOptions.vault) : undefined;\n if (vault && !fs.existsSync(vault)) {\n throw Error(`Vault \"${vault}\" doesn't exist`)\n }\n \n // download plugins and themes to cache\n const plugins = await this.obsidianLauncher.downloadPlugins(\n (obsidianOptions.plugins ?? [])\n .concat([this.helperPluginPath]) // Always install the helper plugin\n .map(p => resolveEntry(this.rootDir, p) as PluginEntry)\n );\n\n const themes = await this.obsidianLauncher.downloadThemes(\n (obsidianOptions.themes ?? [])\n .map(t => resolveEntry(this.rootDir, t) as ThemeEntry),\n );\n\n let appVersion = cap.browserVersion ?? cap[OBSIDIAN_CAPABILITY_KEY]?.appVersion ?? \"latest\";\n appVersion = (await this.obsidianLauncher.getVersionInfo(appVersion)).version;\n if (semver.lt(appVersion, minSupportedObsidianVersion)) {\n throw Error(`Minimum supported Obsidian version is ${minSupportedObsidianVersion}`)\n }\n\n if (isAppium(cap)) {\n let apk = getAppiumOptions(cap).app;\n if (!apk) {\n apk = await this.obsidianLauncher.downloadAndroid(appVersion);\n }\n let chromedriverDir = getAppiumOptions(cap).chromedriverExecutableDir;\n if (!chromedriverDir) {\n chromedriverDir = path.join(this.obsidianLauncher.cacheDir, 'appium-chromedriver');\n }\n\n const normalizedObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...obsidianOptions,\n plugins, themes, vault: vault,\n appVersion, installerVersion: appVersion,\n emulateMobile: false,\n }\n cap[OBSIDIAN_CAPABILITY_KEY] = normalizedObsidianOptions;\n cap['appium:app'] = apk;\n cap['appium:chromedriverExecutableDir'] = chromedriverDir;\n cap[\"wdio:enforceWebDriverClassic\"] = true; // BiDi doesn't seem to work on Obsidian mobile\n if (!getAppiumOptions(cap)['noReset']) {\n console.warn(\"Note: For best performance set noReset to true, wdio-obsidian-service will handle resetting Obsidian between tests.\")\n }\n } else {\n const [, installerVersion] = await this.obsidianLauncher.resolveVersion(\n appVersion, obsidianOptions.installerVersion ?? \"earliest\",\n );\n const installerInfo = await this.obsidianLauncher.getInstallerInfo(installerVersion);\n\n let installerPath: string;\n if (obsidianOptions.binaryPath) {\n installerPath = path.resolve(this.rootDir, obsidianOptions.binaryPath)\n } else {\n installerPath = await this.obsidianLauncher.downloadInstaller(installerVersion);\n }\n let appPath: string;\n if (obsidianOptions.appPath) {\n appPath = path.resolve(this.rootDir, obsidianOptions.appPath)\n } else {\n appPath = await this.obsidianLauncher.downloadApp(appVersion);\n }\n let chromedriverPath = cap['wdio:chromedriverOptions']?.binary;\n // wdio can't download chromedriver for versions less than 115 automatically. Fetching it ourselves is\n // also a bit faster as it skips the chromedriver version detection step.\n if (!chromedriverPath) {\n chromedriverPath = await this.obsidianLauncher.downloadChromedriver(installerVersion);\n }\n\n const normalizedObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...obsidianOptions,\n plugins, themes, vault,\n binaryPath: installerPath, appPath: appPath,\n appVersion, installerVersion,\n emulateMobile: obsidianOptions.emulateMobile ?? false,\n }\n\n cap.browserName = \"chrome\";\n cap.browserVersion = installerInfo.chrome;\n cap[OBSIDIAN_CAPABILITY_KEY] = normalizedObsidianOptions;\n cap['goog:chromeOptions'] = {\n binary: installerPath,\n windowTypes: [\"app\", \"webview\"],\n ...cap['goog:chromeOptions'],\n args: [\n // Workaround for SUID issue on linux. See https://github.com/electron/electron/issues/42510\n ...(process.platform == 'linux' ? [\"--no-sandbox\"] : []),\n ...(cap['goog:chromeOptions']?.args ?? [])\n ],\n }\n cap['wdio:chromedriverOptions'] = {\n // allowedIps is not included in the types, but gets passed as --allowed-ips to chromedriver.\n // It defaults to [\"0.0.0.0\"] which makes Windows Firewall complain, and we don't need remote\n // connections anyways.\n allowedIps: [],\n ...cap['wdio:chromedriverOptions'],\n binary: chromedriverPath,\n } as WebdriverIO.ChromedriverOptions;\n cap[\"wdio:enforceWebDriverClassic\"] = true; // electron doesn't support BiDi yet.\n }\n }\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n}\n\n\n/**\n * wdio worker service.\n * Use in wdio.conf.mts like so:\n * ```ts\n * services: ['obsidian'],\n * ```\n * @hidden\n */\nexport class ObsidianWorkerService implements Services.ServiceInstance {\n private obsidianLauncher: ObsidianLauncher\n private browser: WebdriverIO.Browser|undefined;\n /** Directories to clean up after the tests */\n private tmpDirs: string[]\n /** Path on Android devices to store temporary vaults */\n private androidVaultDir = \"/storage/emulated/0/Documents/wdio-obsidian-service-vaults\";\n\n constructor (\n public options: ObsidianServiceOptions,\n public capabilities: WebdriverIO.Capabilities,\n public config: Options.Testrunner\n ) {\n this.obsidianLauncher = new ObsidianLauncher({\n cacheDir: config.cacheDir ?? getDefaultCacheDir(config.rootDir || process.cwd()),\n versionsUrl: options.versionsUrl,\n communityPluginsUrl: options.communityPluginsUrl, communityThemesUrl: options.communityThemesUrl,\n });\n this.tmpDirs = [];\n }\n\n /**\n * Creates a copy of the vault with plugins and themes installed\n */\n private async setupVault(cap: WebdriverIO.Capabilities) {\n const obsidianOptions = getNormalizedObsidianOptions(cap);\n let vaultCopy: string|undefined;\n if (obsidianOptions.vault != undefined) {\n log.info(`Opening vault ${obsidianOptions.vault}`);\n vaultCopy = await this.obsidianLauncher.setupVault({\n vault: obsidianOptions.vault,\n copy: true,\n plugins: obsidianOptions.plugins,\n themes: obsidianOptions.themes,\n });\n this.tmpDirs.push(vaultCopy);\n } else {\n log.info(`Opening Obsidian without a vault`)\n }\n obsidianOptions.vaultCopy = vaultCopy; // for use in getVaultPath() and the other service hooks\n }\n\n /**\n * Sets up the --user-data-dir for the Electron app. Sets the obsidian.json in the dir to open the vault on boot.\n */\n private async electronSetupConfigDir(cap: WebdriverIO.Capabilities) {\n const obsidianOptions = getNormalizedObsidianOptions(cap);\n const configDir = await this.obsidianLauncher.setupConfigDir({\n appVersion: obsidianOptions.appVersion, installerVersion: obsidianOptions.installerVersion,\n appPath: obsidianOptions.appPath,\n vault: obsidianOptions.vaultCopy,\n // `app.emulateMobile` just sets this localStorage variable. Setting it ourselves here instead of calling\n // the function simplifies the boot/plugin load sequence and makes sure plugins load in mobile mode.\n localStorage: obsidianOptions.emulateMobile ? {\"EmulateMobile\": \"1\"} : {},\n });\n this.tmpDirs.push(configDir);\n\n cap['goog:chromeOptions'] = {\n ...cap['goog:chromeOptions'],\n args: [\n `--user-data-dir=${configDir}`,\n ...(cap['goog:chromeOptions']?.args ?? []).filter(arg => {\n const match = arg.match(/^--user-data-dir=(.*)$/);\n return !match || !this.tmpDirs.includes(match[1]);\n })\n ]\n }\n }\n\n /**\n * Sets up the Obsidian app. Installs and launches it if needed, and sets permissions and contexts.\n * \n * You can configure Appium to install and launch the app for you. However, if you do that it reboots the app after\n * every session/spec which is really slow. So we are handling the app setup manually here.\n */\n private async appiumSetupApp() {\n const browser = this.browser!;\n const appiumOptions = getAppiumOptions(browser.requestedCapabilities);\n const obsidianOptions = getNormalizedObsidianOptions(browser.requestedCapabilities);\n const appId = \"md.obsidian\";\n\n // install/reinstall Obsidian if needed\n const dumpsys: string = await browser.execute(\"mobile: shell\", {command: \"dumpsys\", args: [\"package\", appId]});\n const installedObsidianVersion = dumpsys.match(/versionName[=:](.*)$/m)?.[1]?.trim();\n if (installedObsidianVersion != obsidianOptions.appVersion) {\n await browser.execute('mobile: terminateApp', {appId}); // terminate app (if running)\n await browser.execute('mobile: removeApp', {appId, keepData: false}); // uninstall (if present)\n await browser.execute('mobile: installApp', {\n appPath: appiumOptions.app, // the APK\n timeout: appiumOptions.androidInstallTimeout, // respect appium configuration\n grantPermissions: true, // this and autoGrantPermissions don't seem to really work, see below\n });\n }\n\n // start app if needed\n const appState: number = await browser.execute(\"mobile: queryAppState\", {appId});\n if (appState < 4) { // 0: not installed, 1: not running, 3: running in background, 4: running in foreground\n await browser.execute(\"mobile: activateApp\", {appId});\n }\n\n // grant Obsidian the permissions it needs, mainly file access.\n // appium:autoGrantPermissions is supposed to automatically grant everything it needs, but I can't get it to\n // work. The \"mobile: changePermissions\" \"all\" option also doesn't seem to work here. I have to explicitly list\n // the permissions and use the \"appops\" target. See https://github.com/appium/appium/issues/19991\n // I'm calling \"mobile: changePermissions\" \"all\" as well just in case it is actually doing something\n await browser.execute(\"mobile: changePermissions\", {\n action: \"grant\",\n appPackage: appId,\n permissions: \"all\",\n });\n await browser.execute(\"mobile: changePermissions\", {\n action: \"allow\",\n appPackage: appId,\n permissions: [ // these are from apk AndroidManifest.xml (extracted with apktool)\n \"READ_EXTERNAL_STORAGE\", \"WRITE_EXTERNAL_STORAGE\", \"MANAGE_EXTERNAL_STORAGE\",\n ],\n target: \"appops\", // requires appium --allow-insecure adb_shell\n });\n\n // switch to the webview context\n const context = \"WEBVIEW_md.obsidian\";\n await browser.waitUntil(async () => (await browser.getContexts() as string[]).includes(context));\n await browser.switchContext(context);\n\n // Clear app state\n await browser.execute(() => { // in case tests get interrupted during the `_capacitor_file_` bit\n if (window.location.href != \"http://localhost/\") {\n window.location.replace('http://localhost/');\n }\n })\n if (!obsidianOptions.vault) { // skip if vault is set, as we'll clear stat when we open the new vault\n await this.appiumCloseVault();\n }\n }\n\n /**\n * Opens the vault in appium.\n */\n private async appiumOpenVault() {\n const browser = this.browser!;\n const obsidianOptions = getNormalizedObsidianOptions(browser.requestedCapabilities);\n const androidVault = `${this.androidVaultDir}/${path.basename(obsidianOptions.vaultCopy!)}`;\n // TODO: Capabilities is not really the right place to be storing state like vaultCopy and uploadVault\n obsidianOptions.uploadedVault = androidVault;\n // transfer the vault to the device\n await appiumUploadFiles(browser, {src: obsidianOptions.vaultCopy!, dest: androidVault});\n\n // open vault by setting the localStorage keys and relaunching Obsidian\n // on appium restarting the app with appium:fullReset is *really* slow. And, unlike electron we can actually\n // switch vault with just a reload. So for Appium, instead of rebooting we manually wipe localStorage and use\n // reload to switch the vault.\n await browser.execute(async (androidVault) => {\n localStorage.clear();\n localStorage.setItem('mobile-external-vaults', JSON.stringify([androidVault]));\n localStorage.setItem('mobile-selected-vault', androidVault);\n // appId on mobile is just the full vault path\n localStorage.setItem(`enable-plugin-${androidVault}`, 'true');\n window.location.reload();\n }, androidVault);\n }\n\n /**\n * Close any open vault and go back to the vault switcher\n */\n private async appiumCloseVault() {\n const browser = this.browser!;\n await browser.execute(async () => {\n if (localStorage.length > 0) { // skip if we're already on the vault switcher\n localStorage.clear();\n location.reload();\n }\n });\n }\n\n /**\n * Waits for Obsidian to be ready, and does some other final setup.\n */\n private async prepareApp() {\n const browser = this.browser!;\n const obsidianOptions = getNormalizedObsidianOptions(browser.requestedCapabilities);\n\n if (obsidianOptions.emulateMobile && obsidianOptions.vault != undefined) {\n // I don't think this is technically necessary, but when you set the window size via emulateMobile it sets\n // the window size in the browser view, but not the actual window size which looks weird and makes it hard\n // to manually debug a paused test. The normal ways to set window size don't work on Obsidian. Obsidian\n // doesn't respect the `--window-size` argument, and wdio setViewport and setWindowSize don't work without\n // BiDi. This resizes the window directly using electron APIs.\n await browser.waitUntil(() => browser.execute(() => !!(window as any).electron));\n const [width, height] = await browser.execute(() => [window.innerWidth, window.innerHeight]);\n await browser.execute(async (width, height) => {\n await (window as any).electron.remote.getCurrentWindow().setSize(width, height);\n }, width, height);\n }\n\n // wait until app is loaded\n if (obsidianOptions.vault) {\n await browser.waitUntil( // wait until the helper plugin is loaded\n () => browser.execute(() => !!(window as any).wdioObsidianService),\n {timeout: 30 * 1000, interval: 100},\n );\n await browser.executeObsidian(async ({app}) => {\n await new Promise<void>((resolve) => app.workspace.onLayoutReady(resolve));\n });\n } else {\n await browser.execute(async () => {\n if (document.readyState === \"loading\") {\n return new Promise<void>(resolve => document.addEventListener(\"DOMContentLoaded\", () => resolve()));\n }\n });\n }\n }\n\n private createReloadObsidian() {\n const service = this; // eslint-disable-line @typescript-eslint/no-this-alias\n const reloadObsidian: WebdriverIO.Browser['reloadObsidian'] = async function(\n this: WebdriverIO.Browser,\n {vault, plugins, theme} = {},\n ) {\n const oldObsidianOptions = getNormalizedObsidianOptions(this.requestedCapabilities);\n const selectedPlugins = selectPlugins(oldObsidianOptions.plugins, plugins);\n const selectedThemes = selectThemes(oldObsidianOptions.themes, theme);\n if (!vault && oldObsidianOptions.vaultCopy == undefined) {\n throw Error(`No vault is open, pass a vault path to reloadObsidian`);\n }\n const newObsidianOptions: NormalizedObsidianCapabilityOptions = {\n ...oldObsidianOptions,\n // Resolve relative to PWD instead of root dir during tests\n vault: vault ? path.resolve(vault) : oldObsidianOptions.vault,\n plugins: selectedPlugins, themes: selectedThemes,\n };\n\n if (isAppium(this.requestedCapabilities)) {\n this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] = newObsidianOptions;\n if (vault) {\n await service.setupVault(this.requestedCapabilities);\n await service.appiumOpenVault();\n } else {\n // reload without resetting app state or triggering appium:fullReset\n\n // hack to disable the Obsidian app and make sure it doesn't write to the vault while we modify it\n await this.execute(() => {\n window.location.replace('http://localhost/_capacitor_file_/not-a-file');\n });\n\n // while Obsidian is down, modify the vault files to setup plugins and themes\n const local = path.join(oldObsidianOptions.vaultCopy!, \".obsidian\");\n const localCommunityPlugins = path.join(local, \"community-plugins.json\");\n const localAppearance = path.join(local, \"appearance.json\");\n const remote = `${oldObsidianOptions.uploadedVault!}/.obsidian`;\n const remoteCommunityPlugins = `${remote}/community-plugins.json`;\n const remoteAppearance = `${remote}/appearance.json`;\n\n await appiumDownloadFile(this, remoteCommunityPlugins, localCommunityPlugins).catch(() => {});\n await appiumDownloadFile(this, remoteAppearance, localAppearance).catch(() => {});\n await service.obsidianLauncher.setupVault({\n vault: oldObsidianOptions.vaultCopy!, copy: false,\n plugins: selectedPlugins, themes: selectedThemes,\n });\n let files = [localCommunityPlugins, localAppearance];\n files = (await Promise.all(files.map(async f => await fileExists(f) ? f : \"\"))).filter(f => f);\n await appiumUploadFiles(this, {src: local, dest: remote, files});\n\n // switch the app back\n await this.execute(() => {\n window.location.replace('http://localhost/');\n })\n }\n } else {\n // if browserName is set, reloadSession tries to restart the driver entirely, so unset those\n const newCap: WebdriverIO.Capabilities = _.cloneDeep(\n _.omit(this.requestedCapabilities, ['browserName', 'browserVersion'])\n );\n newCap[OBSIDIAN_CAPABILITY_KEY] = newObsidianOptions;\n\n if (vault) {\n await service.setupVault(newCap);\n await service.electronSetupConfigDir(newCap);\n await this.reloadSession(newCap);\n } else {\n // reload preserving current vault and config dir\n\n // Obsidian debounces saves to the config dir, and so changes to configuration made in the tests may\n // not get saved to disk before the reboot. I haven't found a better way to flush everything than\n // just waiting a bit.\n await this.pause(2000);\n\n await this.deleteSession({shutdownDriver: false});\n // while Obsidian is down, modify the vault files to setup plugins and themes\n await service.obsidianLauncher.setupVault({\n vault: oldObsidianOptions.vaultCopy!, copy: false,\n plugins: selectedPlugins, themes: selectedThemes,\n });\n await this.reloadSession(newCap);\n }\n }\n await service.prepareApp();\n };\n return reloadObsidian;\n }\n\n /**\n * Runs before the session and browser have started.\n */\n async beforeSession(config: Options.Testrunner, cap: WebdriverIO.Capabilities) {\n try {\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n if (cap[OBSIDIAN_CAPABILITY_KEY].vault != undefined) {\n await this.setupVault(cap);\n }\n if (!isAppium(cap)) {\n await this.electronSetupConfigDir(cap);\n }\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n\n /**\n * Runs after session and browser have started, but before tests.\n */\n async before(cap: WebdriverIO.Capabilities, specs: unknown, browser: WebdriverIO.Browser) {\n this.browser = browser;\n try {\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n\n // You are supposed to add commands via the addCommand hook, however you can't add synchronous methods that\n // way. Also, addCommand completely breaks the stack traces of errors from the methods, while tacking on the\n // methods manually doesn't.\n const newBrowserCommands = {\n ...browserCommands,\n reloadObsidian: this.createReloadObsidian(),\n }\n for (const [name, cmd] of Object.entries(newBrowserCommands)) {\n (browser as any)[name] = cmd;\n }\n\n if (isAppium(browser.requestedCapabilities)) {\n await this.appiumSetupApp();\n if (cap[OBSIDIAN_CAPABILITY_KEY].vault) {\n await this.appiumOpenVault();\n }\n }\n await this.prepareApp();\n } catch (e: any) {\n throw new SevereServiceError(getServiceErrorMessage(e));\n }\n }\n\n /** Runs after tests are done, but before the session is shut down */\n async after(result: number, cap: WebdriverIO.Capabilities) {\n const browser = this.browser!;\n if (!cap[OBSIDIAN_CAPABILITY_KEY]) return;\n\n if (isAppium(cap)) {\n await this.appiumCloseVault();\n // \"mobile: deleteFile\" doesn't work on folders. \"mobile:shell\" requires appium --allow-insecure adb_shell\n await browser.execute(\"mobile: shell\", {command: \"rm\", args: [\"-rf\", this.androidVaultDir]});\n }\n }\n\n /**\n * Cleanup\n */\n async afterSession() {\n for (const tmpDir of this.tmpDirs) {\n await fsAsync.rm(tmpDir, { recursive: true, force: true });\n }\n }\n}\n","import * as path from \"path\"\nimport * as fsAsync from \"fs/promises\"\nimport * as crypto from \"crypto\";\nimport { fileURLToPath } from \"url\";\nimport { TFile } from \"obsidian\";\nimport _ from \"lodash\";\nimport { OBSIDIAN_CAPABILITY_KEY, NormalizedObsidianCapabilityOptions } from \"../types.js\";\nimport { BasePage } from \"./basePage.js\";\nimport { isAppium, normalizePath, isHidden, isText } from \"../utils.js\";\nimport { AppInternal } from \"../obsidianTypes.js\";\n\n\n/**\n * Class with various helper methods for writing Obsidian tests using the\n * [page object pattern](https://webdriver.io/docs/pageobjects).\n * \n * You can get an instance of this class either by running\n * ```ts\n * const obsidianPage = await browser.getObsidianPage();\n * ```\n * or just importing it directly with\n * ```ts\n * import { obsidianPage } from \"wdio-obsidian-service\";\n * ```\n * \n * @hideconstructor\n * @category Utilities\n */\nclass ObsidianPage extends BasePage {\n private getObsidianCapabilities(): NormalizedObsidianCapabilityOptions {\n return this.browser.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY];\n }\n\n /**\n * Returns the path to the vault opened in Obsidian.\n * \n * wdio-obsidian-service copies your vault before running tests, so this is the path to the temporary copy.\n */\n getVaultPath(): string {\n const obsidianOptions = this.getObsidianCapabilities();\n if (obsidianOptions.vaultCopy === undefined) {\n throw Error(\"No vault open, set vault in wdio.conf.mts or use reloadObsidian to open a vault dynamically.\")\n }\n return obsidianOptions.uploadedVault ?? obsidianOptions.vaultCopy;\n }\n\n /**\n * Return the Obsidian config dir (just \".obsidian\" unless you changed the config dir name in settings).\n */\n async getConfigDir(): Promise<string> {\n return await this.browser.executeObsidian(({app}) => app.vault.configDir)\n }\n\n /**\n * Returns the Obsidian Platform object. Useful for skipping tests based on whether you are running in desktop or\n * mobile, or based on OS.\n */\n async getPlatform(): Promise<Platform> {\n const obsidianOptions = this.getObsidianCapabilities();\n if (obsidianOptions.vault !== undefined) {\n return await this.browser.executeObsidian(({obsidian}) => {\n const p = obsidian.Platform;\n return {\n isDesktop: p.isDesktop,\n isMobile: p.isMobile,\n isDesktopApp: p.isDesktopApp,\n isMobileApp: p.isMobileApp,\n isIosApp: p.isIosApp,\n isAndroidApp: p.isAndroidApp,\n isPhone: p.isPhone,\n isTablet: p.isTablet,\n isMacOS: p.isMacOS,\n isWin: p.isWin,\n isLinux: p.isLinux,\n isSafari: p.isSafari,\n };\n });\n } else {\n // hack to allow calling getPlatform before opening a vault. This is needed so you can use getPlatform to\n // skip a test before wasting time opening the vault. we don't use this method the rest of the time as you\n // can technically change the size or switch emulation mode during tests\n const appium = isAppium(this.browser.requestedCapabilities);\n const emulateMobile = obsidianOptions.emulateMobile;\n const [width, height] = await this.browser.execute(() => [window.innerWidth, window.innerHeight]);\n\n let isTablet = false;\n let isPhone = false;\n if (appium || emulateMobile) {\n // replicate Obsidian's tablet vs phone breakpoint\n isTablet = (width >= 600 && height >= 600);\n isPhone = !isTablet;\n }\n\n return {\n isDesktop: !(appium || emulateMobile),\n isMobile: appium || emulateMobile,\n isDesktopApp: !appium,\n isMobileApp: appium,\n isIosApp: false, // iOS is not supported\n isAndroidApp: appium,\n isPhone: isPhone,\n isTablet: isTablet,\n isMacOS: !appium && process.platform == 'darwin',\n isWin: !appium && process.platform == 'win32',\n isLinux: !appium && process.platform == 'linux',\n isSafari: false, // iOS is not supported\n };\n }\n }\n\n /**\n * Enables a plugin by ID\n */\n async enablePlugin(pluginId: string): Promise<void> {\n await this.browser.executeObsidian(\n async ({app}, pluginId) => await (app as AppInternal).plugins.enablePluginAndSave(pluginId),\n pluginId,\n );\n }\n\n /**\n * Disables a plugin by ID\n */\n async disablePlugin(pluginId: string): Promise<void> {\n await this.browser.executeObsidian(\n async ({app}, pluginId) => await (app as AppInternal).plugins.disablePluginAndSave(pluginId),\n pluginId,\n );\n }\n\n /**\n * Sets the theme. Pass \"default\" to reset to the Obsidian theme.\n */\n async setTheme(themeName: string): Promise<void> {\n themeName = themeName == 'default' ? '' : themeName;\n await this.browser.executeObsidian(\n async ({app}, themeName) => await (app as AppInternal).customCss.setTheme(themeName),\n themeName,\n )\n }\n\n /**\n * Opens a file in a new tab.\n */\n async openFile(path: string) {\n await this.browser.executeObsidian(async ({app, obsidian}, path) => {\n const file = app.vault.getAbstractFileByPath(path);\n if (file instanceof obsidian.TFile) {\n const leaf = app.workspace.getLeaf('tab');\n await leaf.openFile(file);\n app.workspace.setActiveLeaf(leaf, {focus: true});\n } else {\n throw Error(`No file ${path} exists`);\n }\n }, path)\n }\n\n /**\n * Loads a saved workspace layout from `.obsidian/workspaces.json` by name. Use the core \"Workspaces\"\n * plugin to create the layouts. You can also pass the layout object directly.\n */\n async loadWorkspaceLayout(layout: any): Promise<void> {\n if (typeof layout == \"string\") {\n // read from .obsidian/workspaces.json like the built-in workspaces plugin does\n const workspacesPath = `${await this.getConfigDir()}/workspaces.json`;\n const layoutName = layout;\n try {\n const fileContent = await this.browser.executeObsidian(async ({app}, workspacesPath) => {\n return await app.vault.adapter.read(workspacesPath);\n }, workspacesPath);\n layout = JSON.parse(fileContent)?.workspaces?.[layoutName];\n } catch {\n throw new Error(`Failed to load ${workspacesPath}:${layoutName}`);\n }\n if (!layout) {\n throw new Error(`No workspace ${layoutName} found in ${workspacesPath}`);\n }\n }\n\n await this.browser.executeObsidian(async ({app}, layout) => {\n await app.workspace.changeLayout(layout)\n }, layout)\n }\n\n /**\n * Deletes a file or folder in the vault.\n * @param file path of inside vault, e.g. \"/books/leviathan-wakes.md\"\n */\n async delete(file: string) {\n await this.browser.executeObsidian(async ({app, obsidian}, file, isVaultFile) => {\n file = obsidian.normalizePath(file);\n if (isVaultFile) {\n const fileObj = app.vault.getAbstractFileByPath(file);\n if (fileObj) {\n await app.vault.delete(fileObj, true);\n };\n } else {\n const stat = await app.vault.adapter.stat(file);\n if (stat && stat.type == \"folder\") {\n await app.vault.adapter.rmdir(file, true);\n } else if (stat) {\n await app.vault.adapter.remove(file);\n }\n }\n }, file, !isHidden(file));\n }\n\n /**\n * Writes to a file in the vault. Creates parent directories if needed.\n * @param file path of inside vault, e.g. \"/books/leviathan-wakes.md\"\n * @param content content to write to the file\n */\n async write(file: string, content: string|ArrayBuffer) {\n let strContent: string|undefined, binContent: string|undefined;\n if (typeof content == \"string\") {\n strContent = content;\n } else {\n binContent = Buffer.from(content).toString(\"base64\");\n }\n await this.browser.executeObsidian(async ({app, obsidian}, file, isVaultFile, strContent, binContent) => {\n file = obsidian.normalizePath(file);\n const parent = file.split(\"/\").slice(0, -1).join(\"/\");\n // there's a bug in Obsidian Android where occasionally creating a file silently fails, leaving just an\n // empty file. So retry if that happens. See https://forum.obsidian.md/t/102935\n let success = false;\n let retries = 0;\n while (!success && retries < 8) {\n if (isVaultFile) {\n if (parent && !app.vault.getAbstractFileByPath(parent)) {\n await app.vault.createFolder(parent);\n }\n const fileObj = app.vault.getAbstractFileByPath(file);\n strContent = strContent ?? atob(binContent!);\n if (fileObj) {\n await app.vault.modify(fileObj as TFile, strContent);\n } else {\n await app.vault.create(file, strContent);\n }\n } else {\n await app.vault.adapter.mkdir(parent);\n if (strContent) {\n await app.vault.adapter.write(file, strContent);\n } else {\n const buffer = Uint8Array.from(atob(binContent!), c => c.charCodeAt(0)).buffer;\n await app.vault.adapter.writeBinary(file, buffer);\n }\n }\n\n success = (\n !obsidian.Platform.isAndroidApp ||\n (strContent?.length ?? binContent!.length) === 0 ||\n (await app.vault.adapter.stat(file))!.size > 0\n );\n retries++;\n }\n if (!success) {\n throw new Error(`Failed to write file ${file}`);\n }\n }, file, !isHidden(file) && isText(file), strContent, binContent);\n }\n\n /**\n * Create a folder in the vault. Creates parent directories if needed.\n * @param file path of inside vault, e.g. \"/books\"\n */\n async mkdir(file: string) {\n await this.browser.executeObsidian(async ({app, obsidian}, file, isVaultFile) => {\n file = obsidian.normalizePath(file);\n if (isVaultFile) {\n if (parent && !app.vault.getAbstractFileByPath(file)) {\n await app.vault.createFolder(file);\n }\n } else {\n await app.vault.adapter.mkdir(file);\n }\n }, file, !isHidden(file));\n }\n\n /**\n * Updates the vault by modifying files in place without reloading Obsidian. Can be used to reset the vault back to\n * its original state or to \"switch\" to an entirely different vault without rebooting Obsidian\n * \n * This will only update regular vault files, it won't touch anything under `.obsidian`, and it won't reset any\n * Obsidian config or plugin settings. But if all you need is to reset the vault files, this can be used as a faster\n * alternative to {@link ObsidianBrowserCommands.reloadObsidian | reloadObsidian}.\n * \n * You'll often want to combine resetVault with something like this to reset your plugin's configuration as well:\n * ```ts\n * await browser.executeObsidian(async ({plugins}, settings) => {\n * Object.assign(plugins.myPlugin.settings, settings);\n * await plugins.myPlugin.saveSettings();\n * }, {...});\n * ```\n *\n * If no vault is passed, it resets the vault back to the oringal vault opened by the tests. You can also pass a\n * path to a different vault, and it will replace the current files with the files of that vault (similar to an\n * \"rsync\"). Or, instead of passing a vault path you can pass an object mapping vault file paths to file content.\n * E.g.\n * ```ts\n * obsidianPage.resetVault({\n * 'path/in/vault.md': \"Hello World\",\n * })\n * ```\n * \n * You can also pass multiple vaults and objects, and they will be merged. This can be useful if you want to add a\n * few small modifications to the base vault. e.g:\n * ```ts\n * obsidianPage.resetVault('./path/to/vault', {\n * \"books/leviathan-wakes.md\": \"...\",\n * })\n * ```\n */\n async resetVault(...vaults: (string|Record<string, string|ArrayBuffer>)[]) {\n const obsidianOptions = this.getObsidianCapabilities();\n if (!obsidianOptions.vault) {\n // open an empty vault if there's no vault open\n const defaultVaultPath = path.resolve(fileURLToPath(import.meta.url), '../../default-vault');\n await this.browser.reloadObsidian({vault: defaultVaultPath});\n }\n const configDir = await this.getConfigDir();\n vaults = vaults.length == 0 ? [obsidianOptions.vault!] : vaults;\n\n // list all files in the new vault\n type NewFileInfo = {type: \"file\"|\"folder\", sourceContent?: string|ArrayBuffer, sourceFile?: string};\n const newVault: Map<string, NewFileInfo> = new Map();\n for (let vault of vaults) {\n if (typeof vault == \"string\") {\n vault = path.resolve(vault);\n const files = await fsAsync.readdir(vault, { recursive: true, withFileTypes: true });\n for (const f of files) {\n const fullPath = path.join(f.parentPath, f.name);\n const vaultPath = path.relative(vault, fullPath).split(path.sep).join(\"/\");\n if (!vaultPath.startsWith(configDir + \"/\")) {\n if (f.isDirectory()) {\n newVault.set(vaultPath, {type: 'folder'});\n } else {\n newVault.set(vaultPath, {type: 'file', sourceFile: fullPath});\n }\n }\n }\n } else {\n vault = _.mapKeys(vault, (v, k) => normalizePath(k));\n for (const [file, content] of Object.entries(vault)) {\n newVault.set(file, {type: \"file\", sourceContent: content});\n }\n const folders = new Set(Object.keys(vault).map(p => path.posix.dirname(p)).filter(p => p !== \".\"));\n for (const folder of folders) {\n newVault.set(folder, {type: \"folder\"});\n }\n }\n }\n\n // list all files in the current vault\n type CurrFileInfo = {type: \"file\"|\"folder\", hash?: string};\n const currVault = new Map(await this.browser.executeObsidian(({app}, configDir) => {\n async function hash(data: ArrayBuffer) {\n const hashBuffer = await window.crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n }\n\n async function listRecursive(path: string): Promise<[string, CurrFileInfo][]> {\n const result: [string, CurrFileInfo][] = [];\n const { folders, files } = await app.vault.adapter.list(path);\n for (const folder of folders) {\n if (!folder.startsWith(configDir + \"/\")) {\n result.push([folder, {type: \"folder\"}]);\n result.push(...await listRecursive(folder));\n }\n }\n for (const file of files) {\n if (!file.startsWith(configDir + \"/\")) {\n let fileHash = (app as AppInternal).metadataCache.getFileInfo(file)?.hash;\n if (!fileHash) { // hidden files etc. aren't in Obsidian's metadata cache\n fileHash = await hash(await app.vault.adapter.readBinary(file));\n }\n result.push([file, { type: \"file\", hash: fileHash }]);\n }\n }\n return result;\n }\n\n return listRecursive(\"/\");\n }, configDir));\n\n // delete any files that need to be deleted\n for (const file of [...currVault.keys()].sort().reverse()) {\n const currFileInfo = currVault.get(file)!\n if (!newVault.has(file) || newVault.get(file)!.type != currFileInfo.type) {\n await this.delete(file);\n }\n }\n\n // create files and folders\n for (const [file, newFileInfo] of _.sortBy([...newVault.entries()], 0)) {\n const currFileInfo = currVault.get(file);\n if (newFileInfo.type == \"file\") {\n let content = newFileInfo.sourceContent;\n if (!content) {\n content = (await fsAsync.readFile(newFileInfo.sourceFile!)).buffer as ArrayBuffer;\n }\n const hash = crypto.createHash(\"SHA256\")\n .update(typeof content == \"string\" ? content : new Uint8Array(content))\n .digest(\"hex\");\n if (!currFileInfo || currFileInfo.hash != hash) {\n await this.write(file, content);\n }\n } else if (newFileInfo.type == \"folder\" && !currFileInfo) {\n await this.mkdir(file);\n }\n }\n }\n}\n\n/**\n * Info on the platform we are running on or emulating, in similar format as\n * [obsidian.Platform](https://docs.obsidian.md/Reference/TypeScript+API/Platform)\n * @category Types\n */\nexport interface Platform {\n /**\n * The UI is in desktop mode.\n */\n isDesktop: boolean;\n /**\n * The UI is in mobile mode.\n */\n isMobile: boolean;\n /**\n * We're running the Electron-based desktop app.\n * Note, when running under `emulateMobile` this will still be true and isDesktop will be false.\n */\n isDesktopApp: boolean;\n /**\n * We're running the Capacitor-js mobile app.\n * Note, when running under `emulateMobile` this will still be false and isMobile will be true.\n */\n isMobileApp: boolean;\n /** \n * We're running the iOS app.\n * Note, wdio-obsidian-service doesn't support iOS yet, so this will always be false.\n */\n isIosApp: boolean;\n /**\n * We're running the Android app.\n */\n isAndroidApp: boolean;\n /**\n * We're in a mobile app that has very limited screen space.\n */\n isPhone: boolean;\n /**\n * We're in a mobile app that has sufficiently large screen space.\n */\n isTablet: boolean;\n /**\n * We're on a macOS device, or a device that pretends to be one (like iPhones and iPads).\n * Typically used to detect whether to use command-based hotkeys vs ctrl-based hotkeys.\n */\n isMacOS: boolean;\n /**\n * We're on a Windows device.\n */\n isWin: boolean;\n /**\n * We're on a Linux device.\n */\n isLinux: boolean;\n /**\n * We're running in Safari.\n * Note, wdio-obsidian-service doesn't support iOS yet, so this will always be false.\n */\n isSafari: boolean;\n};\n\n/**\n * Instance of {@link ObsidianPage} with helper methods for writing Obsidian tests.\n * @category Utilities\n */\nconst obsidianPage = new ObsidianPage()\nexport default obsidianPage;\nexport { ObsidianPage };\n","import type { ObsidianBrowserCommands } from \"./browserCommands.js\";\nimport type { PluginEntry, ThemeEntry, DownloadedPluginEntry, DownloadedThemeEntry } from \"obsidian-launcher\";\n\nexport const OBSIDIAN_CAPABILITY_KEY = \"wdio:obsidianOptions\";\n\n/**\n * Options passed to an \"wdio:obsidianOptions\" capability in wdio.conf.mts. E.g.\n * ```ts\n * // ...\n * capabilities: [{\n * browserName: \"obsidian\",\n * browserVersion: \"latest\",\n * 'wdio:obsidianOptions': {\n * installerVersion: 'earliest',\n * plugins: [\".\"],\n * },\n * }],\n * ```\n * \n * @category Options\n */\nexport interface ObsidianCapabilityOptions {\n /**\n * Version of Obsidian to download and run.\n * \n * Can be set to a specific version or one of:\n * - \"latest\": Run the latest non-beta Obsidian version\n * - \"latest-beta\": Run the latest beta Obsidian version (or latest is there is no current beta)\n * - To download Obsidian beta versions you'll need to have an Obsidian Insiders account and either set the \n * `OBSIDIAN_EMAIL` and `OBSIDIAN_PASSWORD` env vars (`.env` file is supported) or pre-download the Obsidian \n * beta version with `npx obsidian-launcher download app -v latest-beta`\n * - \"earliest\": Run the `minAppVersion` set in your `manifest.json`\n * \n * Defaults to \"latest\".\n * \n * You can also use the wdio capability `browserVersion` field to set the Obsidian version.\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n */\n appVersion?: string\n\n /**\n * Version of the Obsidian installer to download and run.\n * \n * Obsidian is Desktop distributed in two parts, the app which contains the JS, and the installer which is the\n * binary with Electron. Obsidian's auto update only updates the app, so users on the same Obsidian version can be\n * running different Electron versions. You can use this to test your plugin against different installer/Electron\n * versions.\n * \n * See also: [Obsidian App vs Installer Versions](../README.md#obsidian-app-vs-installer-versions)\n *\n * Can be set to a specific version string or one of:\n * - \"latest\": Run the latest Obsidian installer compatible with `appVersion`.\n * - \"earliest\": Run the oldest Obsidian installer compatible with `appVersion`.\n * \n * Defaults to \"earliest\".\n */\n installerVersion?: string,\n\n /**\n * List of plugins to install.\n * \n * Each entry is a path to the local plugin to install, e.g. [\".\"] or [\"dist\"] depending on your build setup. Paths\n * are relative to your `wdio.conf.mts`. You can also pass objects. If you pass an object it should contain one of\n * `path` (to install a local plugin), `repo` (to install a plugin from GitHub), or `id` (to install a community\n * plugin). You can set `enabled: false` to install the plugin but start it disabled. You can enable the plugin\n * later using {@link ObsidianBrowserCommands.reloadObsidian|browser.reloadObsidian} or\n * {@link ObsidianPage.enablePlugin}.\n */\n plugins?: PluginEntry[],\n\n /**\n * List of themes to install.\n * \n * Each entry is a path to the local theme to install. Paths are relative to your `wdio.conf.mts`. You can also pass\n * an object. If you pass an object it should contain one of `path` (to install a local theme), `repo` (to install a\n * theme from GitHub), or `name` (to install a community theme). You can set `enabled: false` to install the theme,\n * but start it disabled. You can only have one enabled theme, so if you pass multiple you'll have to disable all\n * but one.\n */\n themes?: ThemeEntry[],\n\n /**\n * The path to the vault to open.\n * \n * The vault will be copied, so any changes made in your tests won't affect the original. If omitted, no vault will\n * be opened and you'll need to call {@link ObsidianBrowserCommands.reloadObsidian|browser.reloadObsidian} to open a\n * vault during your tests. Path is relative to your `wdio.conf.mts`.\n */\n vault?: string,\n\n /**\n * Set to true to emulate mobile on the Electron desktop app. This uses Obsidian `app.emulateMobile()` to switch\n * Obsidian to the mobile UI, and you can use Chrome's mobileEmulation to set the screen size. You can compare\n * tablet vs phone UIs by setting the screen size or emulated device. Obsidian tablet UI triggers at\n * width/height >= 600.\n *\n * Note that Obsidian Mobile runs on Capacitor instead of Electron so there are various platform differences that\n * can't be emulated. But it's good enough for most cases as long as you aren't interacting directly with the\n * operating system or Electron APIs. You can use an Android Virtual Device instead if you want a more accurate\n * (but slower) mobile test.\n *\n * See [Mobile Emulation](../README.md#mobile-emulation) for more info.\n * See [Android](../README.md#android) if you want to test the real mobile app on an Android Virtual Device instead.\n */\n emulateMobile?: boolean,\n\n /**\n * Path to the Obsidian binary to use. If omitted it will be downloaded automatically.\n */\n binaryPath?: string,\n\n /**\n * Path to the app asar to load into obsidian. If omitted it will be downloaded automatically.\n */\n appPath?: string,\n}\n\n\n/** Internal type, capability options after being normalized by onPrepare */\nexport interface NormalizedObsidianCapabilityOptions {\n appVersion: string\n installerVersion: string,\n plugins: DownloadedPluginEntry[],\n themes: DownloadedThemeEntry[],\n vault?: string,\n vaultCopy?: string,\n /** Path of the vault on the appium device */\n uploadedVault?: string,\n emulateMobile: boolean,\n binaryPath?: string,\n appPath?: string,\n}\n\n\n/**\n * Options passed to wdio-obsidian-service service in wdio.conf.mts. E.g.\n * ```js\n * // ...\n * services: [[\"obsidian\", {versionsUrl: \"file:///path/to/obsidian-versions.json\"}]]\n * ```\n * You'll usually want to leave these options as the default, they are mostly useful for wdio-obsidian-service's\n * internal tests.\n * \n * @category Options\n */\nexport interface ObsidianServiceOptions {\n /**\n * Override the `obsidian-versions.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/jesse-r-s-hines/wdio-obsidian-service/HEAD/obsidian-versions.json\n * which is auto-updated to contain information on available Obsidian versions.\n */\n versionsUrl?: string,\n /**\n * Override the `community-plugins.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-plugins.json\n */\n communityPluginsUrl?: string,\n /**\n * Override the `community-css-themes.json` used by the service. Can be a file URL.\n * Defaults to https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-css-themes.json\n */\n communityThemesUrl?: string,\n}\n\ndeclare global {\n namespace WebdriverIO {\n interface Capabilities {\n [OBSIDIAN_CAPABILITY_KEY]?: ObsidianCapabilityOptions,\n }\n\n interface Browser extends ObsidianBrowserCommands {}\n }\n}\n","// src/index.ts\nvar globals = globalThis._wdioGlobals = globalThis._wdioGlobals || /* @__PURE__ */ new Map();\nvar GLOBALS_ERROR_MESSAGE = `No browser instance registered. Don't import @wdio/globals outside of the WDIO testrunner context. Or you have two two different \"@wdio/globals\" packages installed.`;\nfunction proxyHandler(key) {\n return {\n get: (self, prop) => {\n if (!globals.has(key)) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n const receiver = globals.get(key);\n const field = receiver[prop];\n return typeof field === \"function\" ? field.bind(receiver) : field;\n }\n };\n}\nvar browser = new Proxy(\n class Browser {\n },\n proxyHandler(\"browser\")\n);\nvar driver = new Proxy(\n class Browser2 {\n },\n proxyHandler(\"driver\")\n);\nvar multiremotebrowser = new Proxy(\n class Browser3 {\n },\n proxyHandler(\"multiremotebrowser\")\n);\nvar $ = (...args) => {\n if (!globals.has(\"$\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"$\")(...args);\n};\nvar $$ = (...args) => {\n if (!globals.has(\"$$\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"$$\")(...args);\n};\nvar expect = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\")(...args);\n};\nvar ASYNC_MATCHERS = [\n \"any\",\n \"anything\",\n \"arrayContaining\",\n \"objectContaining\",\n \"stringContaining\",\n \"stringMatching\"\n];\nfor (const matcher of ASYNC_MATCHERS) {\n expect[matcher] = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\")[matcher](...args);\n };\n}\nexpect.not = ASYNC_MATCHERS.reduce((acc, matcher) => {\n acc[matcher] = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n return globals.get(\"expect\").not[matcher](...args);\n };\n return acc;\n}, {});\nexpect.extend = (...args) => {\n if (!globals.has(\"expect\")) {\n throw new Error(GLOBALS_ERROR_MESSAGE);\n }\n const expect2 = globals.get(\"expect\");\n return expect2.extend(...args);\n};\nfunction _setGlobal(key, value, setGlobal = true) {\n globals.set(key, value);\n if (setGlobal) {\n globalThis[key] = value;\n }\n}\nexport {\n $,\n $$,\n _setGlobal,\n browser,\n driver,\n expect,\n multiremotebrowser\n};\n","import * as wdioGlobals from \"@wdio/globals\"\n\n/**\n * Base page object for use in the wdio [page object pattern](https://webdriver.io/docs/pageobjects).\n * \n * You can pass the browser to the page object, which allows using the object even in wdio standalone mode.\n */\nexport class BasePage {\n private _browser: WebdriverIO.Browser|undefined\n constructor(browser?: WebdriverIO.Browser) {\n this._browser = browser;\n }\n\n /**\n * Returns the browser instance.\n * @hidden\n */\n protected get browser(): WebdriverIO.Browser {\n return this._browser ?? wdioGlobals.browser;\n }\n}\n","import path from \"path\";\nimport crypto from \"crypto\";\nimport fsAsync from \"fs/promises\";\nimport * as tar from \"tar\";\nimport _ from \"lodash\";\n\n/** Quote string for use in shell scripts */\nexport function quote(input: string) {\n return `'${input.replace(/'/g, \"'\\\\''\")}'`;\n}\n\nexport async function fileExists(path: string) {\n try {\n await fsAsync.access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Gets the appium options.\n * Handles combining `\"appium:foo\"` and `\"appium:options\": {\"foo\": ...}` style options.\n */\nexport function getAppiumOptions(\n cap: WebdriverIO.Capabilities,\n): Exclude<WebdriverIO.Capabilities['appium:options'], undefined> {\n let result = _.pickBy(cap, (v, k) => k.startsWith(\"appium:\") && k != 'appium:options');\n result = _.mapKeys(result, (v, k) => k.slice(7))\n return {...result, ...cap['appium:options']};\n}\n\n/** Returns true if this capability is for Appium */\nexport function isAppium(cap: WebdriverIO.Capabilities) {\n return getAppiumOptions(cap).automationName?.toLocaleLowerCase() === 'uiautomator2';\n}\n\n/**\n * Upload multiple files at once. Uploads files as a tar for much better performance and to avoid issues with special\n * characters in names. (see https://github.com/appium/appium-android-driver/issues/1004)\n * \n * @param opts.src Directory to copy from (-C in tar)\n * @param opts.dest Directory to copy to\n * @param opts.files Files under src to copy. Defaults to the whole dir.\n * @param opts.chunkSize Size to split the tar into while uploading (to avoid excessive RAM usage)\n */\nexport async function appiumUploadFiles(browser: WebdriverIO.Browser, opts: {\n src: string, dest: string, files?: string[], chunkSize?: number,\n}) {\n let {\n src, dest,\n files = [src],\n chunkSize = 2 * 1024 * 1024,\n } = opts;\n src = path.resolve(src);\n dest = path.posix.normalize(dest).replace(/\\/$/, '');\n files = files.map(f => path.relative(src, f) || \".\");\n if (files.length == 0) return; // nothing to do\n\n // We'll tar the files up and then extract the tar on Android side. This is a lot faster than doing many small\n // pushFiles. Since pushFile requires sending the whole file contents as a base64 string, we'll split up the tar\n // into chunks to keep the size manageable for large vaults.\n\n const tmpDir = \"/data/local/tmp\"\n const slug = crypto.randomBytes(10).toString(\"base64url\").replace(/[-_]/g, '0');\n const stream = tar.create({C: src, gzip: { level: 2 }}, files);\n\n // buffer/chunk size of the tar stream varies. Without compression it appears to be one chunk per file, splitting\n // large files at maxReadSize, plus some chunks for the header. However compression adds another layer that appears\n // to limit the output chunk size to about 16K.\n let buffers: Buffer[] = [];\n let bufferSize = 0;\n let i = 0;\n for await (const chunk of stream) {\n if (bufferSize + chunk.length > chunkSize) {\n const data = Buffer.concat(buffers).toString('base64');\n await browser.pushFile(`${tmpDir}/${slug}-${String(i).padStart(6, '0')}.tar`, data);\n i++;\n buffers = [];\n bufferSize = 0;\n }\n buffers.push(chunk);\n bufferSize += chunk.length;\n }\n const data = Buffer.concat(buffers).toString('base64');\n await browser.pushFile(`${tmpDir}/${slug}-${String(i).padStart(6, '0')}.tar`, data);\n\n // extract the tar. `mobile: shell` does NOT escape arguments despite taking an argument array.\n await browser.execute(\"mobile: shell\", {command: \"sh\", args: [\"-c\", quote(`\n mkdir -p ${quote(dest)};\n cat ${tmpDir}/${slug}-*.tar | tar -xz -C ${quote(dest)};\n rm ${tmpDir}/${slug}-*.tar;\n `)]});\n}\n\nexport async function appiumDownloadFile(browser: WebdriverIO.Browser, src: string, dest: string) {\n src = path.posix.normalize(src);\n dest = path.resolve(dest);\n const content = Buffer.from(await browser.pullFile(src), \"base64\");\n await fsAsync.writeFile(dest, content);\n}\n\n/** Lists all files under a folder. Returns full paths. */\nexport async function appiumReaddir(browser: WebdriverIO.Browser, dir: string) {\n // list all files, one per line, no escaping\n const stdout: string = await browser.execute(\"mobile: shell\", {command: \"ls\", args: [\"-NA1\", dir]});\n return stdout.split(\"\\n\").filter(f => f).map(f => path.join(dir, f)).sort();\n}\n\n/** SHA256 hash */\nexport async function hash(content: string|ArrayBufferLike) {\n return crypto.createHash(\"SHA256\")\n .update(typeof content == \"string\" ? content : new Uint8Array(content))\n .digest(\"hex\");\n}\n\n/** Replicate obsidian.normalizePath */\nexport function normalizePath(p: string) {\n p = p.replace(/([\\\\/])+/g, \"/\").replace(/(^\\/+|\\/+$)/g, \"\");\n if (p == \"\") {\n p = \"/\"\n }\n p = p.replace(/\\u00A0|\\u202F/g, \" \"); // replace special space characters\n p = p.normalize(\"NFC\");\n return p;\n}\n\n/** Returns true if a vault file path is hidden (either it or one of it's parent directories starts with \".\") */\nexport function isHidden(file: string) {\n return file.split(\"/\").some(p => p.startsWith(\".\"))\n}\n\n/** Returns true if this is a simple text file */\nexport function isText(file: string) {\n return [\".md\", \".json\", \".txt\", \".js\"].includes(path.extname(file).toLocaleLowerCase());\n}\n","import type * as obsidian from \"obsidian\"\nimport { ObsidianPage } from \"./pageobjects/obsidianPage.js\"\nimport { NormalizedObsidianCapabilityOptions, OBSIDIAN_CAPABILITY_KEY } from \"./types.js\";\nimport { AppInternal } from \"./obsidianTypes.js\";\n\nexport const browserCommands = {\n /**\n * Wrapper around browser.execute that passes the Obsidian API to the function. The first argument to the function\n * is an object containing keys:\n * - app: Obsidian app instance\n * - obsidian: Full Obsidian API\n * - plugins: Object of all installed plugins, mapped by plugin id converted to camelCase.\n * - require: The customized require function Obsidian makes available to plugins. This is also available globally,\n * so you can just use `require` directly instead of from {@link ExecuteObsidianArg} if you prefer.\n * \n * Like `brower.execute`, you can pass other extra arguments to the function.\n * \n * See also: https://webdriver.io/docs/api/browser/execute\n * \n * Example usage\n * ```ts\n * const file = browser.executeObsidian(({app, obsidian}, path) => {\n * return app.vault.getMarkdownFiles().find(f => f.path == path);\n * })\n * ```\n * \n * Note: The same caveats as `browser.execute` apply. The function will be stringified and then run inside Obsidian,\n * so you can't capture any local variables. E.g.\n * \n * This *won't* work:\n * ```ts\n * import { FileView } from \"obsidian\"\n * browser.executeObsidian(({app}) => {\n * if (leaf.view instanceof FileView) {\n * ...\n * }\n * })\n * ```\n * do this instead:\n * ```ts\n * browser.executeObsidian(({app, obsidian}) => {\n * if (leaf.view instanceof obsidian.FileView) {\n * ...\n * }\n * })\n * ```\n */\n async executeObsidian<Return, Params extends unknown[]>(\n this: WebdriverIO.Browser,\n func: (obs: ExecuteObsidianArg, ...params: Params) => Return,\n ...params: Params\n ): Promise<Return> {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n if (obsidianOptions.vault === undefined) {\n throw Error(\"No vault open, set vault in wdio.conf.mts or use reloadObsidian to open a vault dynamically.\")\n }\n // Call the function. Just stringify the function (browser.execute stringifies it anyways). Add a workaround for\n // node fs errors returning useless error messages. If the thrown error has a \"code\" field that is a string, the\n // chromedriver throws an error like this with no further info:\n // \"unknown error: call function result missing int 'status'\"\n // I think this is a bug in ChromeDevtoolsProtocol or chromedriver. See\n // https://github.com/electron-userland/spectron/issues/1057\n const result = await this.execute<Return, Params>(`\n const require = window.wdioObsidianService().require;\n try {\n return await (\n ${func.toString()}\n ).call(null, window.wdioObsidianService(), ...arguments);\n } catch (e) {\n if (\"code\" in e && typeof e.code != \"number\") {\n delete e.code;\n }\n throw e;\n }\n `, ...params);\n // TODO Should maybe add in the TransformReturn and TransformElement bit that wdio has recently added to the\n // execute types, though it causes weird affects if `func` returns type any.\n return result as Return;\n },\n\n /**\n * Executes an Obsidian command by id.\n * @param id Id of the command to run.\n */\n async executeObsidianCommand(this: WebdriverIO.Browser, id: string) {\n const result = await this.executeObsidian(\n ({app}, id) => (app as AppInternal).commands.executeCommandById(id),\n id,\n );\n if (!result) {\n throw Error(`Obsidian command ${id} not found or failed.`);\n }\n },\n\n /**\n * Returns the Obsidian app version this test is running under.\n */\n getObsidianVersion(this: WebdriverIO.Browser): string {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n return obsidianOptions.appVersion;\n },\n \n /**\n * Returns the Obsidian installer version this test is running under.\n */\n getObsidianInstallerVersion(this: WebdriverIO.Browser): string {\n const obsidianOptions = this.requestedCapabilities[OBSIDIAN_CAPABILITY_KEY] as NormalizedObsidianCapabilityOptions;\n return obsidianOptions.installerVersion;\n },\n\n /**\n * Returns the ObsidianPage object with convenience helper functions.\n * You can also just import the page object directly with\n * ```ts\n * import { obsidianPage } from \"wdio-obsidian-service\"\n * ```\n */\n getObsidianPage(this: WebdriverIO.Browser): ObsidianPage {\n return new ObsidianPage(this);\n },\n}\n\n/**\n * Extra commands added to the WDIO Browser instance.\n * \n * See also: https://webdriver.io/docs/api/browser\n * @interface\n * @category Utilities\n */\nexport type ObsidianBrowserCommands = typeof browserCommands & {\n /**\n * Relaunch obsidian. Can be used to switch to a new vault, change the plugin list, or just to reboot Obsidian.\n * \n * As this does a full reboot of Obsidian, this is rather slow. In many cases you can use\n * {@link ObsidianPage.resetVault} instead, which modifies vault files in place without rebooting Obsidian. If all\n * your tests use the same vault, you can also just set the vault in the `wdio.conf.mts` capabilities section.\n * \n * @param params.vault Path to the vault to open. The vault will be copied, so any changes made in your tests won't\n * be persited to the original. If omitted, it will reboot Obsidian with the current vault without creating a\n * new copy of the vault.\n * @param params.plugins List of plugin ids to enable. If omitted it will keep current plugin list. Note, all the\n * plugins must be defined in your wdio.conf.mts capabilities. You can also use the enablePlugin and \n * disablePlugin commands to change plugins without relaunching Obsidian.\n * @param params.theme Name of the theme to enable. If omitted it will keep the current theme. Pass \"default\" to\n * switch back to the default theme. Like with plugins, the theme must be defined in wdio.conf.mts.\n */\n reloadObsidian(params?: {\n vault?: string,\n plugins?: string[], theme?: string,\n }): Promise<void>;\n // This command is implemented in the service hooks.\n};\n\n/**\n * Argument passed to the {@link ObsidianBrowserCommands.executeObsidian | executeObsidian} browser command.\n * @category Types\n */\nexport interface ExecuteObsidianArg {\n /**\n * There is a global \"app\" instance, but that may be removed in the future so you can use this to access it from\n * tests. See https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines#Avoid+using+global+app+instance\n */\n app: obsidian.App,\n\n /**\n * The full obsidian API. See https://github.com/obsidianmd/obsidian-api/blob/master/obsidian.d.ts\n */\n obsidian: typeof obsidian,\n\n /**\n * Object containing all installed plugins mapped by their id. Plugin ids are converted to camelCase for ease of\n * destructuring.\n * \n * If you want to add typings for your plugin(s) you can use something like this in a `.d.ts`:\n * ```ts\n * import type MyPlugin from \"../src/main.js\"\n * declare module \"wdio-obsidian-service\" {\n * interface InstalledPlugins {\n * myPlugin: MyPlugin,\n * }\n * }\n * ```\n */\n plugins: InstalledPlugins,\n\n /**\n * The customized require function Obsidian makes available to plugins. This is also available globally, so you can\n * just use `require` directly instead of from {@link ExecuteObsidianArg} if you prefer.\n */\n require: NodeJS.Require,\n}\n\n/**\n * Installed plugins, mapped by their id converted to camelCase\n * @category Types\n */\nexport interface InstalledPlugins extends Record<string, obsidian.Plugin> {\n}\n","import { remote } from 'webdriverio'\nimport type { Capabilities, Options } from '@wdio/types'\nimport { ObsidianLauncherService, ObsidianWorkerService } from \"./service.js\"\nimport { ObsidianServiceOptions } from \"./types.js\"\n\n/**\n * Starts an Obsidian instance for WDIO standalone mode.\n * \n * For testing, you'll usually want to use the WDIO testrunner with Mocha, wdio.conf.mts, etc. to launch WDIO. However\n * if you want to use WDIO for some kind of scripting scenario, you can use this function to launch a WDIO standalone\n * session connected to Obsidian.\n * \n * See also: https://webdriver.io/docs/setuptypes/#standalone-mode\n * \n * @category WDIO Helpers\n */\nexport async function startWdioSession(\n params: Capabilities.WebdriverIOConfig,\n serviceOptions?: ObsidianServiceOptions,\n): Promise<WebdriverIO.Browser> {\n serviceOptions = serviceOptions ?? {};\n const capabilities = params.capabilities as WebdriverIO.Capabilities;\n const testRunnerOptions: Options.Testrunner = {\n cacheDir: params.cacheDir,\n };\n const launcherService = new ObsidianLauncherService(\n serviceOptions,\n [capabilities] as WebdriverIO.Capabilities,\n testRunnerOptions,\n );\n const workerService = new ObsidianWorkerService(serviceOptions, capabilities, testRunnerOptions);\n\n await launcherService.onPrepare(testRunnerOptions, [capabilities]);\n await workerService.beforeSession(testRunnerOptions, capabilities);\n\n const browser = await remote(params);\n\n await workerService.before(capabilities, [], browser);\n\n return browser;\n}\n"],"mappings":";AAUA,OAAOA,uBAAsB;AAC7B,SAAS,iBAAiB;;;ACX1B,OAAO,QAAQ;AACf,OAAOC,cAAa;AACpB,OAAOC,WAAU;AACjB,SAAS,0BAA0B;AAEnC,OAAO,YAAY;AACnB,SAAS,iBAAAC,sBAAqB;AAC9B,OAAO,sBAEA;;;ACTP,YAAYC,WAAU;AACtB,YAAYC,cAAa;AACzB,YAAYC,aAAY;AACxB,SAAS,qBAAqB;AAE9B,OAAOC,QAAO;;;ACFP,IAAM,0BAA0B;;;ACFvC,IAAI,UAAU,WAAW,eAAe,WAAW,gBAAgC,oBAAI,IAAI;AAC3F,IAAI,wBAAwB;AAC5B,SAAS,aAAa,KAAK;AACzB,SAAO;AAAA,IACL,KAAK,CAAC,MAAM,SAAS;AACnB,UAAI,CAAC,QAAQ,IAAI,GAAG,GAAG;AACrB,cAAM,IAAI,MAAM,qBAAqB;AAAA,MACvC;AACA,YAAM,WAAW,QAAQ,IAAI,GAAG;AAChC,YAAM,QAAQ,SAAS,IAAI;AAC3B,aAAO,OAAO,UAAU,aAAa,MAAM,KAAK,QAAQ,IAAI;AAAA,IAC9D;AAAA,EACF;AACF;AACA,IAAI,UAAU,IAAI;AAAA,EAChB,MAAM,QAAQ;AAAA,EACd;AAAA,EACA,aAAa,SAAS;AACxB;AACA,IAAI,SAAS,IAAI;AAAA,EACf,MAAM,SAAS;AAAA,EACf;AAAA,EACA,aAAa,QAAQ;AACvB;AACA,IAAI,qBAAqB,IAAI;AAAA,EAC3B,MAAM,SAAS;AAAA,EACf;AAAA,EACA,aAAa,oBAAoB;AACnC;AAaA,IAAI,SAAS,IAAI,SAAS;AACxB,MAAI,CAAC,QAAQ,IAAI,QAAQ,GAAG;AAC1B,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AACA,SAAO,QAAQ,IAAI,QAAQ,EAAE,GAAG,IAAI;AACtC;AACA,IAAI,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,WAAW,WAAW,gBAAgB;AACpC,SAAO,OAAO,IAAI,IAAI,SAAS;AAC7B,QAAI,CAAC,QAAQ,IAAI,QAAQ,GAAG;AAC1B,YAAM,IAAI,MAAM,qBAAqB;AAAA,IACvC;AACA,WAAO,QAAQ,IAAI,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI;AAAA,EAC/C;AACF;AACA,OAAO,MAAM,eAAe,OAAO,CAAC,KAAK,YAAY;AACnD,MAAI,OAAO,IAAI,IAAI,SAAS;AAC1B,QAAI,CAAC,QAAQ,IAAI,QAAQ,GAAG;AAC1B,YAAM,IAAI,MAAM,qBAAqB;AAAA,IACvC;AACA,WAAO,QAAQ,IAAI,QAAQ,EAAE,IAAI,OAAO,EAAE,GAAG,IAAI;AAAA,EACnD;AACA,SAAO;AACT,GAAG,CAAC,CAAC;AACL,OAAO,SAAS,IAAI,SAAS;AAC3B,MAAI,CAAC,QAAQ,IAAI,QAAQ,GAAG;AAC1B,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AACA,QAAM,UAAU,QAAQ,IAAI,QAAQ;AACpC,SAAO,QAAQ,OAAO,GAAG,IAAI;AAC/B;;;ACxEO,IAAM,WAAN,MAAe;AAAA,EAElB,YAAYC,UAA+B;AACvC,SAAK,WAAWA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAc,UAA+B;AACzC,WAAO,KAAK,YAAwB;AAAA,EACxC;AACJ;;;ACpBA,OAAO,UAAU;AACjB,OAAO,YAAY;AACnB,OAAO,aAAa;AACpB,YAAY,SAAS;AACrB,OAAO,OAAO;AAGP,SAAS,MAAM,OAAe;AACjC,SAAO,IAAI,MAAM,QAAQ,MAAM,OAAO,CAAC;AAC3C;AAEA,eAAsB,WAAWC,OAAc;AAC3C,MAAI;AACA,UAAM,QAAQ,OAAOA,KAAI;AACzB,WAAO;AAAA,EACX,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAMO,SAAS,iBACZ,KAC8D;AAC9D,MAAI,SAAS,EAAE,OAAO,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,SAAS,KAAK,KAAK,gBAAgB;AACrF,WAAS,EAAE,QAAQ,QAAQ,CAAC,GAAG,MAAM,EAAE,MAAM,CAAC,CAAC;AAC/C,SAAO,EAAC,GAAG,QAAQ,GAAG,IAAI,gBAAgB,EAAC;AAC/C;AAGO,SAAS,SAAS,KAA+B;AACpD,SAAO,iBAAiB,GAAG,EAAE,gBAAgB,kBAAkB,MAAM;AACzE;AAWA,eAAsB,kBAAkBC,UAA8B,MAEnE;AACC,MAAI;AAAA,IACA;AAAA,IAAK;AAAA,IACL,QAAQ,CAAC,GAAG;AAAA,IACZ,YAAY,IAAI,OAAO;AAAA,EAC3B,IAAI;AACJ,QAAM,KAAK,QAAQ,GAAG;AACtB,SAAO,KAAK,MAAM,UAAU,IAAI,EAAE,QAAQ,OAAO,EAAE;AACnD,UAAQ,MAAM,IAAI,OAAK,KAAK,SAAS,KAAK,CAAC,KAAK,GAAG;AACnD,MAAI,MAAM,UAAU,EAAG;AAMvB,QAAM,SAAS;AACf,QAAM,OAAO,OAAO,YAAY,EAAE,EAAE,SAAS,WAAW,EAAE,QAAQ,SAAS,GAAG;AAC9E,QAAM,SAAa,WAAO,EAAC,GAAG,KAAK,MAAM,EAAE,OAAO,EAAE,EAAC,GAAG,KAAK;AAK7D,MAAI,UAAoB,CAAC;AACzB,MAAI,aAAa;AACjB,MAAI,IAAI;AACR,mBAAiB,SAAS,QAAQ;AAC9B,QAAI,aAAa,MAAM,SAAS,WAAW;AACvC,YAAMC,QAAO,OAAO,OAAO,OAAO,EAAE,SAAS,QAAQ;AACrD,YAAMD,SAAQ,SAAS,GAAG,MAAM,IAAI,IAAI,IAAI,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,QAAQC,KAAI;AAClF;AACA,gBAAU,CAAC;AACX,mBAAa;AAAA,IACjB;AACA,YAAQ,KAAK,KAAK;AAClB,kBAAc,MAAM;AAAA,EACxB;AACA,QAAM,OAAO,OAAO,OAAO,OAAO,EAAE,SAAS,QAAQ;AACrD,QAAMD,SAAQ,SAAS,GAAG,MAAM,IAAI,IAAI,IAAI,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,QAAQ,IAAI;AAGlF,QAAMA,SAAQ,QAAQ,iBAAiB,EAAC,SAAS,MAAM,MAAM,CAAC,MAAM,MAAM;AAAA,mBAC3D,MAAM,IAAI,CAAC;AAAA,cAChB,MAAM,IAAI,IAAI,uBAAuB,MAAM,IAAI,CAAC;AAAA,aACjD,MAAM,IAAI,IAAI;AAAA,KACtB,CAAC,EAAC,CAAC;AACR;AAEA,eAAsB,mBAAmBA,UAA8B,KAAa,MAAc;AAC9F,QAAM,KAAK,MAAM,UAAU,GAAG;AAC9B,SAAO,KAAK,QAAQ,IAAI;AACxB,QAAM,UAAU,OAAO,KAAK,MAAMA,SAAQ,SAAS,GAAG,GAAG,QAAQ;AACjE,QAAM,QAAQ,UAAU,MAAM,OAAO;AACzC;AAiBO,SAAS,cAAc,GAAW;AACrC,MAAI,EAAE,QAAQ,aAAa,GAAG,EAAE,QAAQ,gBAAgB,EAAE;AAC1D,MAAI,KAAK,IAAI;AACT,QAAI;AAAA,EACR;AACA,MAAI,EAAE,QAAQ,kBAAkB,GAAG;AACnC,MAAI,EAAE,UAAU,KAAK;AACrB,SAAO;AACX;AAGO,SAAS,SAAS,MAAc;AACnC,SAAO,KAAK,MAAM,GAAG,EAAE,KAAK,OAAK,EAAE,WAAW,GAAG,CAAC;AACtD;AAGO,SAAS,OAAO,MAAc;AACjC,SAAO,CAAC,OAAO,SAAS,QAAQ,KAAK,EAAE,SAAS,KAAK,QAAQ,IAAI,EAAE,kBAAkB,CAAC;AAC1F;;;AJ3GA,IAAM,eAAN,cAA2B,SAAS;AAAA,EACxB,0BAA+D;AACnE,WAAO,KAAK,QAAQ,sBAAsB,uBAAuB;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAuB;AACnB,UAAM,kBAAkB,KAAK,wBAAwB;AACrD,QAAI,gBAAgB,cAAc,QAAW;AACzC,YAAM,MAAM,8FAA8F;AAAA,IAC9G;AACA,WAAO,gBAAgB,iBAAiB,gBAAgB;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAgC;AAClC,WAAO,MAAM,KAAK,QAAQ,gBAAgB,CAAC,EAAC,IAAG,MAAM,IAAI,MAAM,SAAS;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAAiC;AACnC,UAAM,kBAAkB,KAAK,wBAAwB;AACrD,QAAI,gBAAgB,UAAU,QAAW;AACrC,aAAO,MAAM,KAAK,QAAQ,gBAAgB,CAAC,EAAC,SAAQ,MAAM;AACtD,cAAM,IAAI,SAAS;AACnB,eAAO;AAAA,UACH,WAAW,EAAE;AAAA,UACb,UAAU,EAAE;AAAA,UACZ,cAAc,EAAE;AAAA,UAChB,aAAa,EAAE;AAAA,UACf,UAAU,EAAE;AAAA,UACZ,cAAc,EAAE;AAAA,UAChB,SAAS,EAAE;AAAA,UACX,UAAU,EAAE;AAAA,UACZ,SAAS,EAAE;AAAA,UACX,OAAO,EAAE;AAAA,UACT,SAAS,EAAE;AAAA,UACX,UAAU,EAAE;AAAA,QAChB;AAAA,MACJ,CAAC;AAAA,IACL,OAAO;AAIH,YAAM,SAAS,SAAS,KAAK,QAAQ,qBAAqB;AAC1D,YAAM,gBAAgB,gBAAgB;AACtC,YAAM,CAAC,OAAO,MAAM,IAAI,MAAM,KAAK,QAAQ,QAAQ,MAAM,CAAC,OAAO,YAAY,OAAO,WAAW,CAAC;AAEhG,UAAI,WAAW;AACf,UAAI,UAAU;AACd,UAAI,UAAU,eAAe;AAEzB,mBAAY,SAAS,OAAO,UAAU;AACtC,kBAAU,CAAC;AAAA,MACf;AAEA,aAAO;AAAA,QACH,WAAW,EAAE,UAAU;AAAA,QACvB,UAAU,UAAU;AAAA,QACpB,cAAc,CAAC;AAAA,QACf,aAAa;AAAA,QACb,UAAU;AAAA;AAAA,QACV,cAAc;AAAA,QACd;AAAA,QACA;AAAA,QACA,SAAS,CAAC,UAAU,QAAQ,YAAY;AAAA,QACxC,OAAO,CAAC,UAAU,QAAQ,YAAY;AAAA,QACtC,SAAS,CAAC,UAAU,QAAQ,YAAY;AAAA,QACxC,UAAU;AAAA;AAAA,MACd;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,UAAiC;AAChD,UAAM,KAAK,QAAQ;AAAA,MACf,OAAO,EAAC,IAAG,GAAGE,cAAa,MAAO,IAAoB,QAAQ,oBAAoBA,SAAQ;AAAA,MAC1F;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,UAAiC;AACjD,UAAM,KAAK,QAAQ;AAAA,MACf,OAAO,EAAC,IAAG,GAAGA,cAAa,MAAO,IAAoB,QAAQ,qBAAqBA,SAAQ;AAAA,MAC3F;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,WAAkC;AAC7C,gBAAY,aAAa,YAAY,KAAK;AAC1C,UAAM,KAAK,QAAQ;AAAA,MACf,OAAO,EAAC,IAAG,GAAGC,eAAc,MAAO,IAAoB,UAAU,SAASA,UAAS;AAAA,MACnF;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAASC,OAAc;AACzB,UAAM,KAAK,QAAQ,gBAAgB,OAAO,EAAC,KAAK,SAAQ,GAAGA,UAAS;AAChE,YAAM,OAAO,IAAI,MAAM,sBAAsBA,KAAI;AACjD,UAAI,gBAAgB,SAAS,OAAO;AAChC,cAAM,OAAO,IAAI,UAAU,QAAQ,KAAK;AACxC,cAAM,KAAK,SAAS,IAAI;AACxB,YAAI,UAAU,cAAc,MAAM,EAAC,OAAO,KAAI,CAAC;AAAA,MACnD,OAAO;AACH,cAAM,MAAM,WAAWA,KAAI,SAAS;AAAA,MACxC;AAAA,IACJ,GAAGA,KAAI;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,oBAAoB,QAA4B;AAClD,QAAI,OAAO,UAAU,UAAU;AAE3B,YAAM,iBAAiB,GAAG,MAAM,KAAK,aAAa,CAAC;AACnD,YAAM,aAAa;AACnB,UAAI;AACA,cAAM,cAAc,MAAM,KAAK,QAAQ,gBAAgB,OAAO,EAAC,IAAG,GAAGC,oBAAmB;AACpF,iBAAO,MAAM,IAAI,MAAM,QAAQ,KAAKA,eAAc;AAAA,QACtD,GAAG,cAAc;AACjB,iBAAS,KAAK,MAAM,WAAW,GAAG,aAAa,UAAU;AAAA,MAC7D,QAAQ;AACJ,cAAM,IAAI,MAAM,kBAAkB,cAAc,IAAI,UAAU,EAAE;AAAA,MACpE;AACA,UAAI,CAAC,QAAQ;AACT,cAAM,IAAI,MAAM,gBAAgB,UAAU,aAAa,cAAc,EAAE;AAAA,MAC3E;AAAA,IACJ;AAEA,UAAM,KAAK,QAAQ,gBAAgB,OAAO,EAAC,IAAG,GAAGC,YAAW;AACxD,YAAM,IAAI,UAAU,aAAaA,OAAM;AAAA,IAC3C,GAAG,MAAM;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAAO,MAAc;AACvB,UAAM,KAAK,QAAQ,gBAAgB,OAAO,EAAC,KAAK,SAAQ,GAAGC,OAAM,gBAAgB;AAC7E,MAAAA,QAAO,SAAS,cAAcA,KAAI;AAClC,UAAI,aAAa;AACb,cAAM,UAAU,IAAI,MAAM,sBAAsBA,KAAI;AACpD,YAAI,SAAS;AACT,gBAAM,IAAI,MAAM,OAAO,SAAS,IAAI;AAAA,QACxC;AAAC;AAAA,MACL,OAAO;AACH,cAAM,OAAO,MAAM,IAAI,MAAM,QAAQ,KAAKA,KAAI;AAC9C,YAAI,QAAQ,KAAK,QAAQ,UAAU;AAC/B,gBAAM,IAAI,MAAM,QAAQ,MAAMA,OAAM,IAAI;AAAA,QAC5C,WAAW,MAAM;AACb,gBAAM,IAAI,MAAM,QAAQ,OAAOA,KAAI;AAAA,QACvC;AAAA,MACJ;AAAA,IACJ,GAAG,MAAM,CAAC,SAAS,IAAI,CAAC;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,MAAM,MAAc,SAA6B;AACnD,QAAI,YAA8B;AAClC,QAAI,OAAO,WAAW,UAAU;AAC5B,mBAAa;AAAA,IACjB,OAAO;AACH,mBAAa,OAAO,KAAK,OAAO,EAAE,SAAS,QAAQ;AAAA,IACvD;AACA,UAAM,KAAK,QAAQ,gBAAgB,OAAO,EAAC,KAAK,SAAQ,GAAGA,OAAM,aAAaC,aAAYC,gBAAe;AACrG,MAAAF,QAAO,SAAS,cAAcA,KAAI;AAClC,YAAMG,UAASH,MAAK,MAAM,GAAG,EAAE,MAAM,GAAG,EAAE,EAAE,KAAK,GAAG;AAGpD,UAAI,UAAU;AACd,UAAI,UAAU;AACd,aAAO,CAAC,WAAW,UAAU,GAAG;AAC5B,YAAI,aAAa;AACb,cAAIG,WAAU,CAAC,IAAI,MAAM,sBAAsBA,OAAM,GAAG;AACpD,kBAAM,IAAI,MAAM,aAAaA,OAAM;AAAA,UACvC;AACA,gBAAM,UAAU,IAAI,MAAM,sBAAsBH,KAAI;AACpD,UAAAC,cAAaA,eAAc,KAAKC,WAAW;AAC3C,cAAI,SAAS;AACT,kBAAM,IAAI,MAAM,OAAO,SAAkBD,WAAU;AAAA,UACvD,OAAO;AACH,kBAAM,IAAI,MAAM,OAAOD,OAAMC,WAAU;AAAA,UAC3C;AAAA,QACJ,OAAO;AACH,gBAAM,IAAI,MAAM,QAAQ,MAAME,OAAM;AACpC,cAAIF,aAAY;AACZ,kBAAM,IAAI,MAAM,QAAQ,MAAMD,OAAMC,WAAU;AAAA,UAClD,OAAO;AACH,kBAAM,SAAS,WAAW,KAAK,KAAKC,WAAW,GAAG,OAAK,EAAE,WAAW,CAAC,CAAC,EAAE;AACxE,kBAAM,IAAI,MAAM,QAAQ,YAAYF,OAAM,MAAM;AAAA,UACpD;AAAA,QACJ;AAEA,kBACI,CAAC,SAAS,SAAS,iBAClBC,aAAY,UAAUC,YAAY,YAAY,MAC9C,MAAM,IAAI,MAAM,QAAQ,KAAKF,KAAI,GAAI,OAAO;AAEjD;AAAA,MACJ;AACA,UAAI,CAAC,SAAS;AACV,cAAM,IAAI,MAAM,wBAAwBA,KAAI,EAAE;AAAA,MAClD;AAAA,IACJ,GAAG,MAAM,CAAC,SAAS,IAAI,KAAK,OAAO,IAAI,GAAG,YAAY,UAAU;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAM,MAAc;AACtB,UAAM,KAAK,QAAQ,gBAAgB,OAAO,EAAC,KAAK,SAAQ,GAAGA,OAAM,gBAAgB;AAC7E,MAAAA,QAAO,SAAS,cAAcA,KAAI;AAClC,UAAI,aAAa;AACb,YAAI,UAAU,CAAC,IAAI,MAAM,sBAAsBA,KAAI,GAAG;AAClD,gBAAM,IAAI,MAAM,aAAaA,KAAI;AAAA,QACrC;AAAA,MACJ,OAAO;AACH,cAAM,IAAI,MAAM,QAAQ,MAAMA,KAAI;AAAA,MACtC;AAAA,IACJ,GAAG,MAAM,CAAC,SAAS,IAAI,CAAC;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoCA,MAAM,cAAc,QAAuD;AACvE,UAAM,kBAAkB,KAAK,wBAAwB;AACrD,QAAI,CAAC,gBAAgB,OAAO;AAExB,YAAM,mBAAwB,cAAQ,cAAc,YAAY,GAAG,GAAG,qBAAqB;AAC3F,YAAM,KAAK,QAAQ,eAAe,EAAC,OAAO,iBAAgB,CAAC;AAAA,IAC/D;AACA,UAAM,YAAY,MAAM,KAAK,aAAa;AAC1C,aAAS,OAAO,UAAU,IAAI,CAAC,gBAAgB,KAAM,IAAI;AAIzD,UAAM,WAAqC,oBAAI,IAAI;AACnD,aAAS,SAAS,QAAQ;AACtB,UAAI,OAAO,SAAS,UAAU;AAC1B,gBAAa,cAAQ,KAAK;AAC1B,cAAM,QAAQ,MAAc,iBAAQ,OAAO,EAAE,WAAW,MAAM,eAAe,KAAK,CAAC;AACnF,mBAAW,KAAK,OAAO;AACnB,gBAAM,WAAgB,WAAK,EAAE,YAAY,EAAE,IAAI;AAC/C,gBAAM,YAAiB,eAAS,OAAO,QAAQ,EAAE,MAAW,SAAG,EAAE,KAAK,GAAG;AACzE,cAAI,CAAC,UAAU,WAAW,YAAY,GAAG,GAAG;AACxC,gBAAI,EAAE,YAAY,GAAG;AACjB,uBAAS,IAAI,WAAW,EAAC,MAAM,SAAQ,CAAC;AAAA,YAC5C,OAAO;AACH,uBAAS,IAAI,WAAW,EAAC,MAAM,QAAQ,YAAY,SAAQ,CAAC;AAAA,YAChE;AAAA,UACJ;AAAA,QACJ;AAAA,MACJ,OAAO;AACH,gBAAQI,GAAE,QAAQ,OAAO,CAAC,GAAG,MAAM,cAAc,CAAC,CAAC;AACnD,mBAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,KAAK,GAAG;AACjD,mBAAS,IAAI,MAAM,EAAC,MAAM,QAAQ,eAAe,QAAO,CAAC;AAAA,QAC7D;AACA,cAAM,UAAU,IAAI,IAAI,OAAO,KAAK,KAAK,EAAE,IAAI,OAAU,YAAM,QAAQ,CAAC,CAAC,EAAE,OAAO,OAAK,MAAM,GAAG,CAAC;AACjG,mBAAW,UAAU,SAAS;AAC1B,mBAAS,IAAI,QAAQ,EAAC,MAAM,SAAQ,CAAC;AAAA,QACzC;AAAA,MACJ;AAAA,IACJ;AAIA,UAAM,YAAY,IAAI,IAAI,MAAM,KAAK,QAAQ,gBAAgB,CAAC,EAAC,IAAG,GAAGC,eAAc;AAC/E,qBAAe,KAAK,MAAmB;AACnC,cAAM,aAAa,MAAM,OAAO,OAAO,OAAO,OAAO,WAAW,IAAI;AACpE,cAAM,YAAY,MAAM,KAAK,IAAI,WAAW,UAAU,CAAC;AACvD,eAAO,UAAU,IAAI,OAAK,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAAA,MACtE;AAEA,qBAAe,cAAcR,OAAiD;AAC1E,cAAM,SAAmC,CAAC;AAC1C,cAAM,EAAE,SAAS,MAAM,IAAI,MAAM,IAAI,MAAM,QAAQ,KAAKA,KAAI;AAC5D,mBAAW,UAAU,SAAS;AAC1B,cAAI,CAAC,OAAO,WAAWQ,aAAY,GAAG,GAAG;AACrC,mBAAO,KAAK,CAAC,QAAQ,EAAC,MAAM,SAAQ,CAAC,CAAC;AACtC,mBAAO,KAAK,GAAG,MAAM,cAAc,MAAM,CAAC;AAAA,UAC9C;AAAA,QACJ;AACA,mBAAW,QAAQ,OAAO;AACtB,cAAI,CAAC,KAAK,WAAWA,aAAY,GAAG,GAAG;AACnC,gBAAI,WAAY,IAAoB,cAAc,YAAY,IAAI,GAAG;AACrE,gBAAI,CAAC,UAAU;AACX,yBAAW,MAAM,KAAK,MAAM,IAAI,MAAM,QAAQ,WAAW,IAAI,CAAC;AAAA,YAClE;AACA,mBAAO,KAAK,CAAC,MAAM,EAAE,MAAM,QAAQ,MAAM,SAAS,CAAC,CAAC;AAAA,UACxD;AAAA,QACJ;AACA,eAAO;AAAA,MACX;AAEA,aAAO,cAAc,GAAG;AAAA,IAC5B,GAAG,SAAS,CAAC;AAGb,eAAW,QAAQ,CAAC,GAAG,UAAU,KAAK,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG;AACvD,YAAM,eAAe,UAAU,IAAI,IAAI;AACvC,UAAI,CAAC,SAAS,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,EAAG,QAAQ,aAAa,MAAM;AACtE,cAAM,KAAK,OAAO,IAAI;AAAA,MAC1B;AAAA,IACJ;AAGA,eAAW,CAAC,MAAM,WAAW,KAAKD,GAAE,OAAO,CAAC,GAAG,SAAS,QAAQ,CAAC,GAAG,CAAC,GAAG;AACpE,YAAM,eAAe,UAAU,IAAI,IAAI;AACvC,UAAI,YAAY,QAAQ,QAAQ;AAC5B,YAAI,UAAU,YAAY;AAC1B,YAAI,CAAC,SAAS;AACV,qBAAW,MAAc,kBAAS,YAAY,UAAW,GAAG;AAAA,QAChE;AACA,cAAM,OAAc,mBAAW,QAAQ,EAClC,OAAO,OAAO,WAAW,WAAW,UAAU,IAAI,WAAW,OAAO,CAAC,EACrE,OAAO,KAAK;AACjB,YAAI,CAAC,gBAAgB,aAAa,QAAQ,MAAM;AAC5C,gBAAM,KAAK,MAAM,MAAM,OAAO;AAAA,QAClC;AAAA,MACJ,WAAW,YAAY,QAAQ,YAAY,CAAC,cAAc;AACtD,cAAM,KAAK,MAAM,IAAI;AAAA,MACzB;AAAA,IACJ;AAAA,EACJ;AACJ;AAmEA,IAAM,eAAe,IAAI,aAAa;AACtC,IAAO,uBAAQ;;;AK3dR,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0C3B,MAAM,gBAEF,SACG,QACY;AACf,UAAM,kBAAkB,KAAK,sBAAsB,uBAAuB;AAC1E,QAAI,gBAAgB,UAAU,QAAW;AACrC,YAAM,MAAM,8FAA8F;AAAA,IAC9G;AAOA,UAAM,SAAS,MAAM,KAAK,QAAwB;AAAA;AAAA;AAAA;AAAA,sBAIpC,KAAK,SAAS,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAQ1B,GAAG,MAAM;AAGZ,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,uBAAkD,IAAY;AAChE,UAAM,SAAS,MAAM,KAAK;AAAA,MACtB,CAAC,EAAC,IAAG,GAAGE,QAAQ,IAAoB,SAAS,mBAAmBA,GAAE;AAAA,MAClE;AAAA,IACJ;AACA,QAAI,CAAC,QAAQ;AACT,YAAM,MAAM,oBAAoB,EAAE,uBAAuB;AAAA,IAC7D;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAsD;AAClD,UAAM,kBAAkB,KAAK,sBAAsB,uBAAuB;AAC1E,WAAO,gBAAgB;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,8BAA+D;AAC3D,UAAM,kBAAkB,KAAK,sBAAsB,uBAAuB;AAC1E,WAAO,gBAAgB;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,kBAAyD;AACrD,WAAO,IAAI,aAAa,IAAI;AAAA,EAChC;AACJ;;;ANvGA,OAAO,YAAY;AACnB,OAAOC,QAAO;AAGd,IAAM,MAAM,OAAO,uBAAuB;AAE1C,SAAS,mBAAmB,SAAiB;AACzC,SAAOC,MAAK,QAAQ,SAAS,QAAQ,IAAI,uBAAuB,QAAQ,IAAI,kBAAkB,mBAAmB;AACrH;AAGA,SAAS,uBAAuB,GAAQ;AACpC,SACI;AAAA,EACG,EAAE,KAAK;AAAA;AAGlB;AAEA,SAAS,aAAa,SAAiB,OAAuD;AAC1F,MAAI,OAAO,SAAS,UAAU;AAC1B,WAAOA,MAAK,QAAQ,SAAS,KAAK;AAAA,EACtC,WAAW,UAAU,OAAO;AACxB,WAAO,EAAC,GAAG,OAAO,MAAMA,MAAK,QAAQ,SAAS,MAAM,IAAI,EAAC;AAAA,EAC7D,OAAO;AACH,WAAO;AAAA,EACX;AACJ;AAGA,SAAS,cAAc,gBAAyC,WAA+C;AAC3G,MAAI,cAAc,QAAW;AACzB,UAAM,iBAAiBD,GAAE,WAAW,WAAW,eAAe,IAAI,OAAK,EAAE,EAAE,CAAC;AAC5E,QAAI,eAAe,SAAS,GAAG;AAC3B,YAAM,MAAM,uBAAuB,eAAe,KAAK,IAAI,CAAC,EAAE;AAAA,IAClE;AACA,WAAO,eAAe,IAAI,QAAM;AAAA,MAC5B,GAAG;AAAA,MACH,SAAS,UAAU,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM;AAAA,IACjD,EAAE;AAAA,EACN,OAAO;AACH,WAAO;AAAA,EACX;AACJ;AAGA,SAAS,aAAa,eAAuC,WAA4C;AACrG,MAAI,cAAc,QAAW;AACzB,QAAI,aAAa,aAAa,cAAc,MAAM,OAAK,EAAE,QAAQ,SAAS,GAAG;AACzE,YAAM,MAAM,kBAAkB,SAAS,EAAE;AAAA,IAC7C;AACA,WAAO,cAAc,IAAI,QAAM,EAAC,GAAG,GAAG,SAAS,aAAa,aAAa,EAAE,SAAS,UAAS,EAAE;AAAA,EACnG,OAAO;AACH,WAAO;AAAA,EACX;AACJ;AAEA,SAAS,6BAA6B,KAAoE;AACtG,SAAO,IAAI,uBAAuB;AACtC;AAOO,IAAM,8BAAsC;AAW5C,IAAM,0BAAN,MAAkE;AAAA,EAKrE,YACW,SACA,cACA,QACT;AAHS;AACA;AACA;AAEP,SAAK,UAAU,OAAO,WAAW,QAAQ,IAAI;AAC7C,SAAK,mBAAmB,IAAI,iBAAiB;AAAA,MACzC,UAAU,OAAO,YAAY,mBAAmB,KAAK,OAAO;AAAA,MAC5D,aAAa,QAAQ;AAAA,MACrB,qBAAqB,QAAQ;AAAA,MAAqB,oBAAoB,QAAQ;AAAA,IAClF,CAAC;AACD,SAAK,mBAAmBC,MAAK,QAAQC,eAAc,YAAY,GAAG,GAAG,qBAAqB;AAAA,EAC9F;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,QAA4B,cAAmD;AAC3F,QAAI;AACA,UAAI,CAAC,MAAM,QAAQ,YAAY,GAAG;AAC9B,uBAAe,OAAO,OAAO,YAAY,EAAE;AAAA,UACvC,CAAC,sBAAuB,kBAA6D;AAAA,QACzF;AAAA,MACJ;AAEA,YAAM,uBAAuB,aAAa,QAAQ,CAAC,QAAQ;AACvD,YAAK,iBAAiB,OAAQ,IAAI,gBAAgB,YAAY;AAC1D,iBAAO,CAAC,GAA+B;AAAA,QAC3C,OAAO;AACH,iBAAO,CAAC;AAAA,QACZ;AAAA,MACJ,CAAC;AAED,iBAAW,OAAO,sBAAsB;AACpC,cAAM,kBAAkB,IAAI,uBAAuB,KAAK,CAAC;AAGzD,cAAM,QAAQ,gBAAgB,QAAQD,MAAK,QAAQ,KAAK,SAAS,gBAAgB,KAAK,IAAI;AAC1F,YAAI,SAAS,CAAC,GAAG,WAAW,KAAK,GAAG;AAChC,gBAAM,MAAM,UAAU,KAAK,iBAAiB;AAAA,QAChD;AAGA,cAAM,UAAU,MAAM,KAAK,iBAAiB;AAAA,WACvC,gBAAgB,WAAW,CAAC,GACxB,OAAO,CAAC,KAAK,gBAAgB,CAAC,EAC9B,IAAI,OAAK,aAAa,KAAK,SAAS,CAAC,CAAgB;AAAA,QAC9D;AAEA,cAAM,SAAS,MAAM,KAAK,iBAAiB;AAAA,WACtC,gBAAgB,UAAU,CAAC,GACvB,IAAI,OAAK,aAAa,KAAK,SAAS,CAAC,CAAe;AAAA,QAC7D;AAEA,YAAI,aAAa,IAAI,kBAAkB,IAAI,uBAAuB,GAAG,cAAc;AACnF,sBAAc,MAAM,KAAK,iBAAiB,eAAe,UAAU,GAAG;AACtE,YAAI,OAAO,GAAG,YAAY,2BAA2B,GAAG;AACpD,gBAAM,MAAM,yCAAyC,2BAA2B,EAAE;AAAA,QACtF;AAEA,YAAI,SAAS,GAAG,GAAG;AACf,cAAI,MAAM,iBAAiB,GAAG,EAAE;AAChC,cAAI,CAAC,KAAK;AACN,kBAAM,MAAM,KAAK,iBAAiB,gBAAgB,UAAU;AAAA,UAChE;AACA,cAAI,kBAAkB,iBAAiB,GAAG,EAAE;AAC5C,cAAI,CAAC,iBAAiB;AAClB,8BAAkBA,MAAK,KAAK,KAAK,iBAAiB,UAAU,qBAAqB;AAAA,UACrF;AAEA,gBAAM,4BAAiE;AAAA,YACnE,GAAG;AAAA,YACH;AAAA,YAAS;AAAA,YAAQ;AAAA,YACjB;AAAA,YAAY,kBAAkB;AAAA,YAC9B,eAAe;AAAA,UACnB;AACA,cAAI,uBAAuB,IAAI;AAC/B,cAAI,YAAY,IAAI;AACpB,cAAI,kCAAkC,IAAI;AAC1C,cAAI,8BAA8B,IAAI;AACtC,cAAI,CAAC,iBAAiB,GAAG,EAAE,SAAS,GAAG;AACnC,oBAAQ,KAAK,qHAAqH;AAAA,UACtI;AAAA,QACJ,OAAO;AACH,gBAAM,CAAC,EAAE,gBAAgB,IAAI,MAAM,KAAK,iBAAiB;AAAA,YACrD;AAAA,YAAY,gBAAgB,oBAAoB;AAAA,UACpD;AACA,gBAAM,gBAAgB,MAAM,KAAK,iBAAiB,iBAAiB,gBAAgB;AAEnF,cAAI;AACJ,cAAI,gBAAgB,YAAY;AAC5B,4BAAgBA,MAAK,QAAQ,KAAK,SAAS,gBAAgB,UAAU;AAAA,UACzE,OAAO;AACH,4BAAgB,MAAM,KAAK,iBAAiB,kBAAkB,gBAAgB;AAAA,UAClF;AACA,cAAI;AACJ,cAAI,gBAAgB,SAAS;AACzB,sBAAUA,MAAK,QAAQ,KAAK,SAAS,gBAAgB,OAAO;AAAA,UAChE,OAAO;AACH,sBAAU,MAAM,KAAK,iBAAiB,YAAY,UAAU;AAAA,UAChE;AACA,cAAI,mBAAmB,IAAI,0BAA0B,GAAG;AAGxD,cAAI,CAAC,kBAAkB;AACnB,+BAAmB,MAAM,KAAK,iBAAiB,qBAAqB,gBAAgB;AAAA,UACxF;AAEA,gBAAM,4BAAiE;AAAA,YACnE,GAAG;AAAA,YACH;AAAA,YAAS;AAAA,YAAQ;AAAA,YACjB,YAAY;AAAA,YAAe;AAAA,YAC3B;AAAA,YAAY;AAAA,YACZ,eAAe,gBAAgB,iBAAiB;AAAA,UACpD;AAEA,cAAI,cAAc;AAClB,cAAI,iBAAiB,cAAc;AACnC,cAAI,uBAAuB,IAAI;AAC/B,cAAI,oBAAoB,IAAI;AAAA,YACxB,QAAQ;AAAA,YACR,aAAa,CAAC,OAAO,SAAS;AAAA,YAC9B,GAAG,IAAI,oBAAoB;AAAA,YAC3B,MAAM;AAAA;AAAA,cAEF,GAAI,QAAQ,YAAY,UAAU,CAAC,cAAc,IAAI,CAAC;AAAA,cACtD,GAAI,IAAI,oBAAoB,GAAG,QAAQ,CAAC;AAAA,YAC5C;AAAA,UACJ;AACA,cAAI,0BAA0B,IAAI;AAAA;AAAA;AAAA;AAAA,YAI9B,YAAY,CAAC;AAAA,YACb,GAAG,IAAI,0BAA0B;AAAA,YACjC,QAAQ;AAAA,UACZ;AACA,cAAI,8BAA8B,IAAI;AAAA,QAC1C;AAAA,MACJ;AAAA,IACJ,SAAS,GAAQ;AACb,YAAM,IAAI,mBAAmB,uBAAuB,CAAC,CAAC;AAAA,IAC1D;AAAA,EACJ;AACJ;AAWO,IAAM,wBAAN,MAAgE;AAAA,EAQnE,YACW,SACA,cACA,QACT;AAHS;AACA;AACA;AALX;AAAA,SAAQ,kBAAkB;AAOtB,SAAK,mBAAmB,IAAI,iBAAiB;AAAA,MACzC,UAAU,OAAO,YAAY,mBAAmB,OAAO,WAAW,QAAQ,IAAI,CAAC;AAAA,MAC/E,aAAa,QAAQ;AAAA,MACrB,qBAAqB,QAAQ;AAAA,MAAqB,oBAAoB,QAAQ;AAAA,IAClF,CAAC;AACD,SAAK,UAAU,CAAC;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,WAAW,KAA+B;AACpD,UAAM,kBAAkB,6BAA6B,GAAG;AACxD,QAAI;AACJ,QAAI,gBAAgB,SAAS,QAAW;AACpC,UAAI,KAAK,iBAAiB,gBAAgB,KAAK,EAAE;AACjD,kBAAY,MAAM,KAAK,iBAAiB,WAAW;AAAA,QAC/C,OAAO,gBAAgB;AAAA,QACvB,MAAM;AAAA,QACN,SAAS,gBAAgB;AAAA,QACzB,QAAQ,gBAAgB;AAAA,MAC5B,CAAC;AACD,WAAK,QAAQ,KAAK,SAAS;AAAA,IAC/B,OAAO;AACH,UAAI,KAAK,kCAAkC;AAAA,IAC/C;AACA,oBAAgB,YAAY;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,uBAAuB,KAA+B;AAChE,UAAM,kBAAkB,6BAA6B,GAAG;AACxD,UAAM,YAAY,MAAM,KAAK,iBAAiB,eAAe;AAAA,MACzD,YAAY,gBAAgB;AAAA,MAAY,kBAAkB,gBAAgB;AAAA,MAC1E,SAAS,gBAAgB;AAAA,MACzB,OAAO,gBAAgB;AAAA;AAAA;AAAA,MAGvB,cAAc,gBAAgB,gBAAgB,EAAC,iBAAiB,IAAG,IAAI,CAAC;AAAA,IAC5E,CAAC;AACD,SAAK,QAAQ,KAAK,SAAS;AAE3B,QAAI,oBAAoB,IAAI;AAAA,MACxB,GAAG,IAAI,oBAAoB;AAAA,MAC3B,MAAM;AAAA,QACF,mBAAmB,SAAS;AAAA,QAC5B,IAAI,IAAI,oBAAoB,GAAG,QAAQ,CAAC,GAAG,OAAO,SAAO;AACrD,gBAAM,QAAQ,IAAI,MAAM,wBAAwB;AAChD,iBAAO,CAAC,SAAS,CAAC,KAAK,QAAQ,SAAS,MAAM,CAAC,CAAC;AAAA,QACpD,CAAC;AAAA,MACL;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,iBAAiB;AAC3B,UAAME,WAAU,KAAK;AACrB,UAAM,gBAAgB,iBAAiBA,SAAQ,qBAAqB;AACpE,UAAM,kBAAkB,6BAA6BA,SAAQ,qBAAqB;AAClF,UAAM,QAAQ;AAGd,UAAM,UAAkB,MAAMA,SAAQ,QAAQ,iBAAiB,EAAC,SAAS,WAAW,MAAM,CAAC,WAAW,KAAK,EAAC,CAAC;AAC7G,UAAM,2BAA2B,QAAQ,MAAM,uBAAuB,IAAI,CAAC,GAAG,KAAK;AACnF,QAAI,4BAA4B,gBAAgB,YAAY;AACxD,YAAMA,SAAQ,QAAQ,wBAAwB,EAAC,MAAK,CAAC;AACrD,YAAMA,SAAQ,QAAQ,qBAAqB,EAAC,OAAO,UAAU,MAAK,CAAC;AACnE,YAAMA,SAAQ,QAAQ,sBAAsB;AAAA,QACxC,SAAS,cAAc;AAAA;AAAA,QACvB,SAAS,cAAc;AAAA;AAAA,QACvB,kBAAkB;AAAA;AAAA,MACtB,CAAC;AAAA,IACL;AAGA,UAAM,WAAmB,MAAMA,SAAQ,QAAQ,yBAAyB,EAAC,MAAK,CAAC;AAC/E,QAAI,WAAW,GAAG;AACd,YAAMA,SAAQ,QAAQ,uBAAuB,EAAC,MAAK,CAAC;AAAA,IACxD;AAOA,UAAMA,SAAQ,QAAQ,6BAA6B;AAAA,MAC/C,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,aAAa;AAAA,IACjB,CAAC;AACD,UAAMA,SAAQ,QAAQ,6BAA6B;AAAA,MAC/C,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,aAAa;AAAA;AAAA,QACT;AAAA,QAAyB;AAAA,QAA0B;AAAA,MACvD;AAAA,MACA,QAAQ;AAAA;AAAA,IACZ,CAAC;AAGD,UAAM,UAAU;AAChB,UAAMA,SAAQ,UAAU,aAAa,MAAMA,SAAQ,YAAY,GAAe,SAAS,OAAO,CAAC;AAC/F,UAAMA,SAAQ,cAAc,OAAO;AAGnC,UAAMA,SAAQ,QAAQ,MAAM;AACxB,UAAI,OAAO,SAAS,QAAQ,qBAAqB;AAC7C,eAAO,SAAS,QAAQ,mBAAmB;AAAA,MAC/C;AAAA,IACJ,CAAC;AACD,QAAI,CAAC,gBAAgB,OAAO;AACxB,YAAM,KAAK,iBAAiB;AAAA,IAChC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,kBAAkB;AAC5B,UAAMA,WAAU,KAAK;AACrB,UAAM,kBAAkB,6BAA6BA,SAAQ,qBAAqB;AAClF,UAAM,eAAe,GAAG,KAAK,eAAe,IAAIF,MAAK,SAAS,gBAAgB,SAAU,CAAC;AAEzF,oBAAgB,gBAAgB;AAEhC,UAAM,kBAAkBE,UAAS,EAAC,KAAK,gBAAgB,WAAY,MAAM,aAAY,CAAC;AAMtF,UAAMA,SAAQ,QAAQ,OAAOC,kBAAiB;AAC1C,mBAAa,MAAM;AACnB,mBAAa,QAAQ,0BAA0B,KAAK,UAAU,CAACA,aAAY,CAAC,CAAC;AAC7E,mBAAa,QAAQ,yBAAyBA,aAAY;AAE1D,mBAAa,QAAQ,iBAAiBA,aAAY,IAAI,MAAM;AAC5D,aAAO,SAAS,OAAO;AAAA,IAC3B,GAAG,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,mBAAmB;AAC7B,UAAMD,WAAU,KAAK;AACrB,UAAMA,SAAQ,QAAQ,YAAY;AAC9B,UAAI,aAAa,SAAS,GAAG;AACzB,qBAAa,MAAM;AACnB,iBAAS,OAAO;AAAA,MACpB;AAAA,IACJ,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,aAAa;AACvB,UAAMA,WAAU,KAAK;AACrB,UAAM,kBAAkB,6BAA6BA,SAAQ,qBAAqB;AAElF,QAAI,gBAAgB,iBAAiB,gBAAgB,SAAS,QAAW;AAMrE,YAAMA,SAAQ,UAAU,MAAMA,SAAQ,QAAQ,MAAM,CAAC,CAAE,OAAe,QAAQ,CAAC;AAC/E,YAAM,CAAC,OAAO,MAAM,IAAI,MAAMA,SAAQ,QAAQ,MAAM,CAAC,OAAO,YAAY,OAAO,WAAW,CAAC;AAC3F,YAAMA,SAAQ,QAAQ,OAAOE,QAAOC,YAAW;AAC3C,cAAO,OAAe,SAAS,OAAO,iBAAiB,EAAE,QAAQD,QAAOC,OAAM;AAAA,MAClF,GAAG,OAAO,MAAM;AAAA,IACpB;AAGA,QAAI,gBAAgB,OAAO;AACvB,YAAMH,SAAQ;AAAA;AAAA,QACV,MAAMA,SAAQ,QAAQ,MAAM,CAAC,CAAE,OAAe,mBAAmB;AAAA,QACjE,EAAC,SAAS,KAAK,KAAM,UAAU,IAAG;AAAA,MACtC;AACA,YAAMA,SAAQ,gBAAgB,OAAO,EAAC,IAAG,MAAM;AAC3C,cAAM,IAAI,QAAc,CAACI,aAAY,IAAI,UAAU,cAAcA,QAAO,CAAC;AAAA,MAC7E,CAAC;AAAA,IACL,OAAO;AACH,YAAMJ,SAAQ,QAAQ,YAAY;AAC9B,YAAI,SAAS,eAAe,WAAW;AACnC,iBAAO,IAAI,QAAc,CAAAI,aAAW,SAAS,iBAAiB,oBAAoB,MAAMA,SAAQ,CAAC,CAAC;AAAA,QACtG;AAAA,MACJ,CAAC;AAAA,IACL;AAAA,EACJ;AAAA,EAEQ,uBAAuB;AAC3B,UAAM,UAAU;AAChB,UAAM,iBAAwD,eAE1D,EAAC,OAAO,SAAS,MAAK,IAAI,CAAC,GAC7B;AACE,YAAM,qBAAqB,6BAA6B,KAAK,qBAAqB;AAClF,YAAM,kBAAkB,cAAc,mBAAmB,SAAS,OAAO;AACzE,YAAM,iBAAiB,aAAa,mBAAmB,QAAQ,KAAK;AACpE,UAAI,CAAC,SAAS,mBAAmB,aAAa,QAAW;AACrD,cAAM,MAAM,uDAAuD;AAAA,MACvE;AACA,YAAM,qBAA0D;AAAA,QAC5D,GAAG;AAAA;AAAA,QAEH,OAAO,QAAQN,MAAK,QAAQ,KAAK,IAAI,mBAAmB;AAAA,QACxD,SAAS;AAAA,QAAiB,QAAQ;AAAA,MACtC;AAEA,UAAI,SAAS,KAAK,qBAAqB,GAAG;AACtC,aAAK,sBAAsB,uBAAuB,IAAI;AACtD,YAAI,OAAO;AACP,gBAAM,QAAQ,WAAW,KAAK,qBAAqB;AACnD,gBAAM,QAAQ,gBAAgB;AAAA,QAClC,OAAO;AAIH,gBAAM,KAAK,QAAQ,MAAM;AACrB,mBAAO,SAAS,QAAQ,8CAA8C;AAAA,UAC1E,CAAC;AAGD,gBAAM,QAAQA,MAAK,KAAK,mBAAmB,WAAY,WAAW;AAClE,gBAAM,wBAAwBA,MAAK,KAAK,OAAO,wBAAwB;AACvE,gBAAM,kBAAkBA,MAAK,KAAK,OAAO,iBAAiB;AAC1D,gBAAMO,UAAS,GAAG,mBAAmB,aAAc;AACnD,gBAAM,yBAAyB,GAAGA,OAAM;AACxC,gBAAM,mBAAmB,GAAGA,OAAM;AAElC,gBAAM,mBAAmB,MAAM,wBAAwB,qBAAqB,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAC5F,gBAAM,mBAAmB,MAAM,kBAAkB,eAAe,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAChF,gBAAM,QAAQ,iBAAiB,WAAW;AAAA,YACtC,OAAO,mBAAmB;AAAA,YAAY,MAAM;AAAA,YAC5C,SAAS;AAAA,YAAiB,QAAQ;AAAA,UACtC,CAAC;AACD,cAAI,QAAQ,CAAC,uBAAuB,eAAe;AACnD,mBAAS,MAAM,QAAQ,IAAI,MAAM,IAAI,OAAM,MAAK,MAAM,WAAW,CAAC,IAAI,IAAI,EAAE,CAAC,GAAG,OAAO,OAAK,CAAC;AAC7F,gBAAM,kBAAkB,MAAM,EAAC,KAAK,OAAO,MAAMA,SAAQ,MAAK,CAAC;AAG/D,gBAAM,KAAK,QAAQ,MAAM;AACrB,mBAAO,SAAS,QAAQ,mBAAmB;AAAA,UAC/C,CAAC;AAAA,QACL;AAAA,MACJ,OAAO;AAEH,cAAM,SAAmCR,GAAE;AAAA,UACvCA,GAAE,KAAK,KAAK,uBAAuB,CAAC,eAAe,gBAAgB,CAAC;AAAA,QACxE;AACA,eAAO,uBAAuB,IAAI;AAElC,YAAI,OAAO;AACP,gBAAM,QAAQ,WAAW,MAAM;AAC/B,gBAAM,QAAQ,uBAAuB,MAAM;AAC3C,gBAAM,KAAK,cAAc,MAAM;AAAA,QACnC,OAAO;AAMH,gBAAM,KAAK,MAAM,GAAI;AAErB,gBAAM,KAAK,cAAc,EAAC,gBAAgB,MAAK,CAAC;AAEhD,gBAAM,QAAQ,iBAAiB,WAAW;AAAA,YACtC,OAAO,mBAAmB;AAAA,YAAY,MAAM;AAAA,YAC5C,SAAS;AAAA,YAAiB,QAAQ;AAAA,UACtC,CAAC;AACD,gBAAM,KAAK,cAAc,MAAM;AAAA,QACnC;AAAA,MACJ;AACA,YAAM,QAAQ,WAAW;AAAA,IAC7B;AACA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,QAA4B,KAA+B;AAC3E,QAAI;AACA,UAAI,CAAC,IAAI,uBAAuB,EAAG;AACnC,UAAI,IAAI,uBAAuB,EAAE,SAAS,QAAW;AACjD,cAAM,KAAK,WAAW,GAAG;AAAA,MAC7B;AACA,UAAI,CAAC,SAAS,GAAG,GAAG;AAChB,cAAM,KAAK,uBAAuB,GAAG;AAAA,MACzC;AAAA,IACJ,SAAS,GAAQ;AACb,YAAM,IAAI,mBAAmB,uBAAuB,CAAC,CAAC;AAAA,IAC1D;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,KAA+B,OAAgBG,UAA8B;AACtF,SAAK,UAAUA;AACf,QAAI;AACA,UAAI,CAAC,IAAI,uBAAuB,EAAG;AAKnC,YAAM,qBAAqB;AAAA,QACvB,GAAG;AAAA,QACH,gBAAgB,KAAK,qBAAqB;AAAA,MAC9C;AACA,iBAAW,CAAC,MAAM,GAAG,KAAK,OAAO,QAAQ,kBAAkB,GAAG;AAC1D,QAACA,SAAgB,IAAI,IAAI;AAAA,MAC7B;AAEA,UAAI,SAASA,SAAQ,qBAAqB,GAAG;AACzC,cAAM,KAAK,eAAe;AAC1B,YAAI,IAAI,uBAAuB,EAAE,OAAO;AACpC,gBAAM,KAAK,gBAAgB;AAAA,QAC/B;AAAA,MACJ;AACA,YAAM,KAAK,WAAW;AAAA,IAC1B,SAAS,GAAQ;AACb,YAAM,IAAI,mBAAmB,uBAAuB,CAAC,CAAC;AAAA,IAC1D;AAAA,EACJ;AAAA;AAAA,EAGA,MAAM,MAAM,QAAgB,KAA+B;AACvD,UAAMA,WAAU,KAAK;AACrB,QAAI,CAAC,IAAI,uBAAuB,EAAG;AAEnC,QAAI,SAAS,GAAG,GAAG;AACf,YAAM,KAAK,iBAAiB;AAE5B,YAAMA,SAAQ,QAAQ,iBAAiB,EAAC,SAAS,MAAM,MAAM,CAAC,OAAO,KAAK,eAAe,EAAC,CAAC;AAAA,IAC/F;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe;AACjB,eAAW,UAAU,KAAK,SAAS;AAC/B,YAAMM,SAAQ,GAAG,QAAQ,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IAC7D;AAAA,EACJ;AACJ;;;AO9mBA,SAAS,cAAc;AAgBvB,eAAsB,iBAClB,QACA,gBAC4B;AAC5B,mBAAiB,kBAAkB,CAAC;AACpC,QAAM,eAAe,OAAO;AAC5B,QAAM,oBAAwC;AAAA,IAC1C,UAAU,OAAO;AAAA,EACrB;AACA,QAAM,kBAAkB,IAAI;AAAA,IACxB;AAAA,IACA,CAAC,YAAY;AAAA,IACb;AAAA,EACJ;AACA,QAAM,gBAAgB,IAAI,sBAAsB,gBAAgB,cAAc,iBAAiB;AAE/F,QAAM,gBAAgB,UAAU,mBAAmB,CAAC,YAAY,CAAC;AACjE,QAAM,cAAc,cAAc,mBAAmB,YAAY;AAEjE,QAAMC,WAAU,MAAM,OAAO,MAAM;AAEnC,QAAM,cAAc,OAAO,cAAc,CAAC,GAAGA,QAAO;AAEpD,SAAOA;AACX;;;ARzBA,IAAO,gBAAQ;AAER,IAAM,WAAW;AAkBxB,eAAsB,sBAAsB,OAAmC,CAAC,GAAG;AAC/E,SAAO,OAAO,QAAQ,WAAW,EAAC,UAAU,KAAI,IAAI;AACpD,QAAMC,YAAW,IAAIC,kBAAiB,IAAI;AAC1C,QAAM,cAAc,MAAMD,UAAS,eAAe,aAAa;AAC/D,SAAO,YAAY,UAAU,MAAMA,UAAS,YAAY,YAAY,OAAO;AAC/E;AAQO,IAAM,0BAA0B,UAAU,eAC7C,YAAoB,kBAA0B,UACrB;AACzB,QAAMA,YAAW,IAAIC,kBAAiB,EAAC,SAAkB,CAAC;AAC1D,SAAO,MAAMD,UAAS,eAAe,YAAY,gBAAgB;AACrE,GAAG,0EAA0E;AAsB7E,eAAsB,sBAClB,UACA,OAA4B,CAAC,GACF;AAC3B,QAAMA,YAAW,IAAIC,kBAAiB,EAAC,UAAU,KAAK,SAAQ,CAAC;AAC/D,SAAOD,UAAS,cAAc,QAAQ;AAC1C;","names":["ObsidianLauncher","fsAsync","path","fileURLToPath","path","fsAsync","crypto","_","browser","path","browser","data","pluginId","themeName","path","workspacesPath","layout","file","strContent","binContent","parent","_","configDir","id","_","path","fileURLToPath","browser","androidVault","width","height","resolve","remote","fsAsync","browser","launcher","ObsidianLauncher"]}
|
package/package.json
CHANGED
|
@@ -1,82 +1,82 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
"
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
2
|
+
"name": "wdio-obsidian-service",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "A WebdriverIO service for end-to-end testing of Obsidian plugins",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"obsidian",
|
|
7
|
+
"webdriverio",
|
|
8
|
+
"wdio",
|
|
9
|
+
"wdio-service",
|
|
10
|
+
"tests"
|
|
11
|
+
],
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"helper-plugin",
|
|
21
|
+
"default-vault"
|
|
22
|
+
],
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/jesse-r-s-hines/wdio-obsidian-service.git",
|
|
26
|
+
"directory": "packages/wdio-obsidian-service"
|
|
27
|
+
},
|
|
28
|
+
"author": "jesse-r-s-hines",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/jesse-r-s-hines/wdio-obsidian-service/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://jesse-r-s-hines.github.io/wdio-obsidian-service/wdio-obsidian-service/README.html",
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/chai": "^5.2.2",
|
|
36
|
+
"@types/lodash": "^4.17.20",
|
|
37
|
+
"@types/mocha": "^10.0.10",
|
|
38
|
+
"@types/node": "^18.15.3",
|
|
39
|
+
"@types/semver": "^7.7.0",
|
|
40
|
+
"@wdio/appium-service": "^9.18.4",
|
|
41
|
+
"@wdio/cli": "^9.18.4",
|
|
42
|
+
"@wdio/globals": "^9.17.0",
|
|
43
|
+
"@wdio/local-runner": "^9.18.4",
|
|
44
|
+
"@wdio/mocha-framework": "^9.18.0",
|
|
45
|
+
"@wdio/spec-reporter": "^9.18.0",
|
|
46
|
+
"@wdio/types": "^9.16.2",
|
|
47
|
+
"chai": "^5.2.1",
|
|
48
|
+
"cross-env": "^7.0.3",
|
|
49
|
+
"mocha": "^10.8.2",
|
|
50
|
+
"ts-node": "^10.9.2",
|
|
51
|
+
"tsup": "^8.5.0",
|
|
52
|
+
"tsx": "^4.20.3",
|
|
53
|
+
"typescript": "^5.9.2",
|
|
54
|
+
"wdio-obsidian-reporter": "2.1.0"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"lodash": "^4.17.21",
|
|
58
|
+
"obsidian-launcher": "2.1.0",
|
|
59
|
+
"semver": "^7.7.2",
|
|
60
|
+
"tar": "^7.4.3"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"@wdio/cli": "^9.18.1",
|
|
64
|
+
"@wdio/local-runner": "^9.18.1",
|
|
65
|
+
"@wdio/logger": "^9.18.0",
|
|
66
|
+
"@wdio/mocha-framework": "^9.18.0",
|
|
67
|
+
"obsidian": "^1.8.7",
|
|
68
|
+
"webdriverio": "^9.18.1"
|
|
69
|
+
},
|
|
70
|
+
"engines": {
|
|
71
|
+
"node": ">=18"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"build": "tsup",
|
|
75
|
+
"test": "pnpm run \"/^test:.*(?<!:e2e)$/\"",
|
|
76
|
+
"test:unit": "tsc --noEmit && mocha --config .mocharc.unit.json",
|
|
77
|
+
"test:e2e": "pnpm run \"/^test:e2e:.*/\"",
|
|
78
|
+
"test:e2e:wdio": "wdio run ./wdio.conf.ts",
|
|
79
|
+
"test:e2e:standalone": "mocha --config .mocharc.standalone.json",
|
|
80
|
+
"test:android": "wdio run ./wdio.mobile.conf.ts"
|
|
81
|
+
}
|
|
82
|
+
}
|