@ishibashi0112/webview2-bridge-test 0.5.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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/dist/bridge.d.ts +34 -0
  4. package/dist/bridge.d.ts.map +1 -0
  5. package/dist/bridge.js +72 -0
  6. package/dist/bridge.js.map +1 -0
  7. package/dist/cli.d.ts +4 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +166 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/clipboard.d.ts +2 -0
  12. package/dist/clipboard.d.ts.map +1 -0
  13. package/dist/clipboard.js +39 -0
  14. package/dist/clipboard.js.map +1 -0
  15. package/dist/config.d.ts +106 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/config.js +65 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/db/diff.d.ts +29 -0
  20. package/dist/db/diff.d.ts.map +1 -0
  21. package/dist/db/diff.js +76 -0
  22. package/dist/db/diff.js.map +1 -0
  23. package/dist/db/env.d.ts +19 -0
  24. package/dist/db/env.d.ts.map +1 -0
  25. package/dist/db/env.js +119 -0
  26. package/dist/db/env.js.map +1 -0
  27. package/dist/db/guard.d.ts +28 -0
  28. package/dist/db/guard.d.ts.map +1 -0
  29. package/dist/db/guard.js +68 -0
  30. package/dist/db/guard.js.map +1 -0
  31. package/dist/db/index.d.ts +72 -0
  32. package/dist/db/index.d.ts.map +1 -0
  33. package/dist/db/index.js +120 -0
  34. package/dist/db/index.js.map +1 -0
  35. package/dist/fixtures.d.ts +25 -0
  36. package/dist/fixtures.d.ts.map +1 -0
  37. package/dist/fixtures.js +167 -0
  38. package/dist/fixtures.js.map +1 -0
  39. package/dist/host.d.ts +41 -0
  40. package/dist/host.d.ts.map +1 -0
  41. package/dist/host.js +154 -0
  42. package/dist/host.js.map +1 -0
  43. package/dist/index.d.ts +9 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +9 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/reporter.d.ts +42 -0
  48. package/dist/reporter.d.ts.map +1 -0
  49. package/dist/reporter.js +221 -0
  50. package/dist/reporter.js.map +1 -0
  51. package/package.json +82 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ishibashi0112
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 ADDED
@@ -0,0 +1,60 @@
1
+ # @ishibashi0112/webview2-bridge-test
2
+
3
+ webview2-bridge で作ったアプリ(WinForms + WebView2 + React)の動作確認を、Playwright のテストコードで置き換えるための部品。
4
+
5
+ - **フィクスチャ**: `page`(WebView2 のページ)/ `bridge`(契約メソッドを直接呼ぶ)/ `db`(Knex を包む DB ヘルパ。前後差分と自動の後片付け)/ `testId`
6
+ - **ホスト起動**: exe を `WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=NNNN` 付きで起動し、CDP で接続する。VB は無改修
7
+ - **レポータ**: 結果を AI チャットに貼れる Markdown(`test-results/report.md`)にまとめ、失敗があればクリップボードへ
8
+ - **doctor**: 会社 PC の前提(ブラウザ / exe と CDP / テスト DB とガード)を 1 コマンドで確認
9
+
10
+ 雛形(`pnpm create webview2-bridge`)には組み込み済み。テストの書き方は雛形の `e2e/README.md`。
11
+
12
+ ## 使い方
13
+
14
+ ```ts
15
+ // playwright.config.ts(アプリのルート)
16
+ import { defineConfig } from "@playwright/test";
17
+ import { playwrightConfig } from "@ishibashi0112/webview2-bridge-test/config";
18
+ import e2e from "./e2e/e2e.config";
19
+ export default defineConfig(playwrightConfig(e2e));
20
+
21
+ // e2e/e2e.config.ts
22
+ import { defineE2EConfig } from "@ishibashi0112/webview2-bridge-test";
23
+ export default defineE2EConfig({
24
+ web: { command: "pnpm dev", url: "http://localhost:5173" },
25
+ host: { exe: "dotnet/MyApp.Host/bin/Debug/net48/MyApp.Host.exe" },
26
+ db: { allowedDatabases: ["MyApp_Test"] },
27
+ });
28
+
29
+ // e2e/host/order.spec.ts
30
+ import { expect, test } from "@ishibashi0112/webview2-bridge-test";
31
+ test("受注を登録すると Orders に 1 行増える", async ({ page, db, testId }) => {
32
+ const before = await db.snapshot([{ table: "Orders", key: ["OrderNo"], where: { CustomerCode: `${testId}-C1` } }]);
33
+ await page.getByTestId("order-submit").click();
34
+ expect((await db.diff(before)).Orders.inserted).toHaveLength(1);
35
+ });
36
+ ```
37
+
38
+ プロジェクトは `screen`(Vite dev + モック。DB なし)/ `api`(実 exe を契約経由で呼ぶ)/ `host`(実 exe を画面操作)の 3 つ。
39
+ `playwright test --project screen` はどこでも、`--project api --project host` は Windows で走る。
40
+
41
+ DB の接続情報は `.env.e2e.local`(`E2E_DB_CLIENT=mssql|oracledb|pg|mysql2|better-sqlite3` と `E2E_DB_HOST` 等)。
42
+ 接続先が `db.allowedDatabases` に無いと起動時に止まる。ドライバ(`tedious` / `oracledb` / `pg` / `mysql2`)はアプリ側で入れる。
43
+
44
+ 環境変数:
45
+
46
+ | 変数 | 用途 |
47
+ |---|---|
48
+ | `E2E_BROWSER_EXECUTABLE` | screen 用ブラウザの実行ファイルを直接指定(Chromium をダウンロードできない環境) |
49
+ | `E2E_BROWSER_CHANNEL` | screen 用のチャンネル(Windows の既定は `msedge`。`""` で Playwright 同梱の Chromium) |
50
+ | `E2E_NO_CLIPBOARD` | レポートをクリップボードにコピーしない |
51
+
52
+ ## 開発
53
+
54
+ ```sh
55
+ pnpm test # Vitest(diff / guard / env / db(sqlite) / reporter / config / bridge / host)
56
+ pnpm typecheck
57
+ ```
58
+
59
+ `test/host.test.ts` は WinForms ホストの代わりにヘッドレス Chromium(`test/fixtures/fake-host.sh`)を起動して CDP 接続を通す。
60
+ Chromium が無ければスキップ。`E2E_BROWSER_EXECUTABLE` で実行ファイルを指定できる。
@@ -0,0 +1,34 @@
1
+ /**
2
+ * L2(API 通し)用: CDP で繋いだページの中でブリッジクライアント(window.__webview2Bridge)を呼ぶ。
3
+ * アプリ側は開発ビルド時(または VITE_EXPOSE_BRIDGE=1)に createClient の戻り値を window に公開しておく。
4
+ */
5
+ import type { Page } from "@playwright/test";
6
+ export declare const DEFAULT_BRIDGE_GLOBAL = "__webview2Bridge";
7
+ export interface BridgeErrorShape {
8
+ name?: string | undefined;
9
+ message: string;
10
+ code?: number | undefined;
11
+ data?: unknown;
12
+ /** BridgeValidationError の issues */
13
+ issues?: unknown;
14
+ }
15
+ /** ページ内で投げられたエラーをテスト側で受け取るための例外。code / data は JSON-RPC の error と同じ */
16
+ export declare class BridgeCallError extends Error {
17
+ name: string;
18
+ readonly method: string;
19
+ readonly code: number | undefined;
20
+ readonly data: unknown;
21
+ readonly issues: unknown;
22
+ readonly remoteName: string | undefined;
23
+ constructor(method: string, shape: BridgeErrorShape);
24
+ }
25
+ export interface Bridge {
26
+ /** 契約メソッドを "ns.name" で呼ぶ。失敗は BridgeCallError */
27
+ call<T = unknown>(method: string, input?: unknown): Promise<T>;
28
+ /** 呼んで失敗することを期待する。成功したら例外 */
29
+ expectError(method: string, input?: unknown): Promise<BridgeCallError>;
30
+ /** window に公開されているか */
31
+ isAvailable(): Promise<boolean>;
32
+ }
33
+ export declare function createBridge(page: Page, globalName?: string): Bridge;
34
+ //# sourceMappingURL=bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AAExD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,qCAAqC;IACrC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,sEAAsE;AACtE,qBAAa,eAAgB,SAAQ,KAAK;IAC/B,IAAI,SAAqB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;gBAC5B,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB;CAQpD;AAED,MAAM,WAAW,MAAM;IACrB,gDAAgD;IAChD,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC/D,6BAA6B;IAC7B,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACvE,uBAAuB;IACvB,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC;AAID,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,GAAE,MAA8B,GAAG,MAAM,CAmD3F"}
package/dist/bridge.js ADDED
@@ -0,0 +1,72 @@
1
+ export const DEFAULT_BRIDGE_GLOBAL = "__webview2Bridge";
2
+ /** ページ内で投げられたエラーをテスト側で受け取るための例外。code / data は JSON-RPC の error と同じ */
3
+ export class BridgeCallError extends Error {
4
+ name = "BridgeCallError";
5
+ method;
6
+ code;
7
+ data;
8
+ issues;
9
+ remoteName;
10
+ constructor(method, shape) {
11
+ super(`${method}: ${shape.name ?? "Error"}${shape.code !== undefined ? ` ${shape.code}` : ""}: ${shape.message}`);
12
+ this.method = method;
13
+ this.code = shape.code;
14
+ this.data = shape.data;
15
+ this.issues = shape.issues;
16
+ this.remoteName = shape.name;
17
+ }
18
+ }
19
+ export function createBridge(page, globalName = DEFAULT_BRIDGE_GLOBAL) {
20
+ const bridge = {
21
+ async isAvailable() {
22
+ return page.evaluate((g) => typeof window[g] === "object", globalName);
23
+ },
24
+ async call(method, input) {
25
+ const result = (await page.evaluate(async ({ g, method, input }) => {
26
+ const root = window[g];
27
+ if (root === undefined || root === null) {
28
+ return { ok: false, error: { name: "BridgeNotExposed", message: `window.${g} がありません。アプリの bridge.ts で開発ビルド時にクライアントを公開してください` } };
29
+ }
30
+ const [ns, name, ...rest] = method.split(".");
31
+ const nsObj = root[ns ?? ""];
32
+ const fn = nsObj !== undefined && nsObj !== null ? nsObj[name ?? ""] : undefined;
33
+ if (rest.length > 0 || typeof fn !== "function") {
34
+ return { ok: false, error: { name: "MethodNotFound", message: `契約に ${method} がありません` } };
35
+ }
36
+ try {
37
+ const value = await fn(input);
38
+ return { ok: true, value };
39
+ }
40
+ catch (e) {
41
+ const err = e;
42
+ return {
43
+ ok: false,
44
+ error: {
45
+ name: typeof err?.name === "string" ? err.name : undefined,
46
+ message: typeof err?.message === "string" ? err.message : String(e),
47
+ code: typeof err?.code === "number" ? err.code : undefined,
48
+ data: err?.data,
49
+ issues: err?.issues,
50
+ },
51
+ };
52
+ }
53
+ }, { g: globalName, method, input }));
54
+ if (result.ok)
55
+ return result.value;
56
+ throw new BridgeCallError(method, result.error);
57
+ },
58
+ async expectError(method, input) {
59
+ try {
60
+ const value = await bridge.call(method, input);
61
+ throw new Error(`${method} は失敗を期待しましたが成功しました: ${JSON.stringify(value)}`);
62
+ }
63
+ catch (e) {
64
+ if (e instanceof BridgeCallError)
65
+ return e;
66
+ throw e;
67
+ }
68
+ },
69
+ };
70
+ return bridge;
71
+ }
72
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ export declare function doctor(argv: string[]): Promise<number>;
3
+ export declare function main(argv: string[]): Promise<number>;
4
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAkEA,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA2F5D;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAI1D"}
package/dist/cli.js ADDED
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `webview2-bridge-test doctor [--config e2e/e2e.config.ts] [--skip-browser] [--skip-host] [--skip-db]`
4
+ * 会社 PC での前提を 1 コマンドで確かめる:
5
+ * 1. Playwright が入っていて、screen 用のブラウザ(Windows は Edge)を起動できる
6
+ * 2. ホスト exe を CDP 付きで起動して WebView2 に接続し、ページを 1 つ取れる
7
+ * 3. .env.e2e.local の設定でテスト DB に繋がり、ガードを通り、1 行読める
8
+ */
9
+ import { existsSync } from "node:fs";
10
+ import path from "node:path";
11
+ import { pathToFileURL } from "node:url";
12
+ import { tsImport } from "tsx/esm/api";
13
+ import { browserUse } from "./config.js";
14
+ import { knexConfigFromEnv, loadEnvFiles } from "./db/env.js";
15
+ import { checkAllowed, describeConnection } from "./db/guard.js";
16
+ import { launchHost } from "./host.js";
17
+ function usage() {
18
+ console.error(`Usage: webview2-bridge-test doctor [--config <e2e.config.ts>] [--skip-browser] [--skip-host] [--skip-db]
19
+
20
+ アプリのルート(playwright.config.ts のある場所)で実行する。既定の設定は e2e/e2e.config.ts`);
21
+ process.exit(2);
22
+ }
23
+ function parseArgs(argv) {
24
+ const o = { config: "e2e/e2e.config.ts", skipBrowser: false, skipHost: false, skipDb: false };
25
+ for (let i = 0; i < argv.length; i++) {
26
+ const a = argv[i];
27
+ if (a === "--config")
28
+ o.config = argv[++i] ?? usage();
29
+ else if (a === "--skip-browser")
30
+ o.skipBrowser = true;
31
+ else if (a === "--skip-host")
32
+ o.skipHost = true;
33
+ else if (a === "--skip-db")
34
+ o.skipDb = true;
35
+ else if (a === "-h" || a === "--help")
36
+ usage();
37
+ else {
38
+ console.error(`Unknown argument: ${a}`);
39
+ usage();
40
+ }
41
+ }
42
+ return o;
43
+ }
44
+ async function loadConfig(file) {
45
+ const full = path.resolve(file);
46
+ if (!existsSync(full))
47
+ throw new Error(`設定ファイルがありません: ${full}`);
48
+ const mod = (await tsImport(pathToFileURL(full).href, import.meta.url));
49
+ const cfg = (mod["default"] ?? mod["e2e"]);
50
+ if (cfg === undefined || typeof cfg !== "object" || typeof cfg.web !== "object") {
51
+ throw new Error(`${file} は defineE2EConfig({...}) を default export してください`);
52
+ }
53
+ return cfg;
54
+ }
55
+ function errorMessage(e) {
56
+ return e instanceof Error ? e.message : String(e);
57
+ }
58
+ export async function doctor(argv) {
59
+ const o = parseArgs(argv);
60
+ const appRoot = process.cwd();
61
+ let cfg;
62
+ try {
63
+ cfg = await loadConfig(o.config);
64
+ }
65
+ catch (e) {
66
+ console.error(`✗ 設定: ${errorMessage(e)}`);
67
+ return 1;
68
+ }
69
+ console.log(`設定: ${o.config} (ルート ${appRoot})`);
70
+ const checks = [];
71
+ if (!o.skipBrowser) {
72
+ checks.push({
73
+ name: "Playwright とブラウザ (screen 用)",
74
+ run: async () => {
75
+ const pw = (await import("@playwright/test"));
76
+ const use = browserUse();
77
+ const launchOptions = { headless: true, ...(use.channel !== undefined && { channel: use.channel }), ...(use.launchOptions ?? {}) };
78
+ const browser = await pw.chromium.launch(launchOptions);
79
+ const version = browser.version();
80
+ await browser.close();
81
+ return `起動 OK (${use.channel ?? use.launchOptions?.executablePath ?? "chromium"} ${version})`;
82
+ },
83
+ });
84
+ }
85
+ if (!o.skipHost) {
86
+ checks.push({
87
+ name: "ホスト exe の起動と CDP 接続 (api / host 用)",
88
+ run: async () => {
89
+ if (cfg.host === undefined)
90
+ return "e2e.config.ts に host が無いためスキップ";
91
+ const pw = (await import("@playwright/test"));
92
+ const app = await launchHost({
93
+ exe: path.resolve(appRoot, cfg.host.exe),
94
+ devUrl: cfg.host.devUrl ?? cfg.web.url,
95
+ cdpPort: cfg.host.cdpPort,
96
+ startupTimeoutMs: cfg.host.startupTimeoutMs,
97
+ env: cfg.host.env,
98
+ chromium: pw.chromium,
99
+ });
100
+ try {
101
+ const url = app.page.url();
102
+ const title = await app.page.title().catch(() => "");
103
+ const exposed = await app.page
104
+ .evaluate((g) => typeof window[g] === "object", cfg.bridgeGlobal ?? "__webview2Bridge")
105
+ .catch(() => false);
106
+ return `接続 OK (${url}${title !== "" ? ` "${title}"` : ""}, window.${cfg.bridgeGlobal ?? "__webview2Bridge"}: ${exposed ? "公開あり" : "未公開 — dev サーバーが起動しているか、bridge.ts の公開行を確認"})`;
107
+ }
108
+ finally {
109
+ await app.close();
110
+ }
111
+ },
112
+ });
113
+ }
114
+ if (!o.skipDb) {
115
+ checks.push({
116
+ name: "テスト DB への接続とガード (api / host 用)",
117
+ run: async () => {
118
+ const loaded = loadEnvFiles(appRoot, cfg.db?.envFiles);
119
+ const knexConfig = knexConfigFromEnv();
120
+ if (knexConfig === undefined) {
121
+ return `E2E_DB_CLIENT が未設定のためスキップ (読んだ env ファイル: ${loaded.length > 0 ? loaded.map((f) => path.relative(appRoot, f)).join(", ") : "なし"})`;
122
+ }
123
+ const desc = describeConnection(knexConfig.client, knexConfig.connection);
124
+ const guard = checkAllowed(desc, cfg.db?.allowedDatabases ?? []);
125
+ if (!guard.ok)
126
+ throw new Error(guard.reason);
127
+ const knexModule = (await import("knex"));
128
+ const knex = knexModule.default(knexConfig);
129
+ try {
130
+ const sql = knexConfig.client === "oracledb" ? "select 1 as ok from dual" : "select 1 as ok";
131
+ await knex.raw(sql);
132
+ return `接続 OK (${desc.label})`;
133
+ }
134
+ finally {
135
+ await knex.destroy();
136
+ }
137
+ },
138
+ });
139
+ }
140
+ let failures = 0;
141
+ for (const c of checks) {
142
+ try {
143
+ const msg = await c.run();
144
+ console.log(`✓ ${c.name}: ${msg}`);
145
+ }
146
+ catch (e) {
147
+ failures++;
148
+ console.log(`✗ ${c.name}: ${errorMessage(e)}`);
149
+ }
150
+ }
151
+ console.log(failures === 0 ? "\nすべて通りました。pnpm test:all を実行できます" : `\n${failures} 件が通りませんでした。上の理由を直してから再実行してください`);
152
+ return failures === 0 ? 0 : 1;
153
+ }
154
+ export async function main(argv) {
155
+ const [command, ...rest] = argv;
156
+ if (command === "doctor")
157
+ return doctor(rest);
158
+ usage();
159
+ }
160
+ // gen / create の cli と同じく bin 専用ファイルなので無条件に実行する(pnpm の bin シムはシンボリックリンク経由で
161
+ // 起動するため、process.argv[1] と import.meta.url の比較では「直接実行」を判定できない)
162
+ main(process.argv.slice(2)).then((code) => process.exit(code), (e) => {
163
+ console.error(`webview2-bridge-test: ${e instanceof Error ? e.message : String(e)}`);
164
+ process.exit(1);
165
+ });
166
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function writeClipboard(text: string): Promise<void>;
2
+ //# sourceMappingURL=clipboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../src/clipboard.ts"],"names":[],"mappings":"AAqBA,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBhE"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * レポートをクリップボードへ(petari の infra/clipboard.ts と同じ方式。OS コマンドを配列引数で呼ぶ)。
3
+ * macOS: pbcopy / Windows: PowerShell Set-Clipboard(UTF-8 の一時ファイル経由)/ Linux: xclip があれば
4
+ */
5
+ import { execFile, spawn } from "node:child_process";
6
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ import { promisify } from "node:util";
10
+ const execFileP = promisify(execFile);
11
+ function viaStdin(command, args, text) {
12
+ return new Promise((resolve, reject) => {
13
+ const child = spawn(command, args, { stdio: ["pipe", "ignore", "pipe"] });
14
+ child.on("error", reject);
15
+ child.on("close", (code) => (code === 0 ? resolve() : reject(new Error(`${command} exited with ${code}`))));
16
+ child.stdin.end(text, "utf8");
17
+ });
18
+ }
19
+ export async function writeClipboard(text) {
20
+ if (process.platform === "darwin") {
21
+ await viaStdin("pbcopy", [], text);
22
+ return;
23
+ }
24
+ if (process.platform === "win32") {
25
+ const dir = mkdtempSync(join(tmpdir(), "wv2b-clip-"));
26
+ const file = join(dir, "report.txt");
27
+ try {
28
+ writeFileSync(file, text, "utf8");
29
+ const quoted = `'${file.replaceAll("'", "''")}'`;
30
+ await execFileP("powershell", ["-NoProfile", "-Command", `Get-Content -Raw -Encoding UTF8 -LiteralPath ${quoted} | Set-Clipboard`]);
31
+ }
32
+ finally {
33
+ rmSync(dir, { recursive: true, force: true });
34
+ }
35
+ return;
36
+ }
37
+ await viaStdin("xclip", ["-selection", "clipboard"], text);
38
+ }
39
+ //# sourceMappingURL=clipboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../src/clipboard.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAEtC,SAAS,QAAQ,CAAC,OAAe,EAAE,IAAc,EAAE,IAAY;IAC7D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QAC1E,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1B,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,OAAO,gBAAgB,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5G,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY;IAC/C,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,MAAM,QAAQ,CAAC,QAAQ,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QACnC,OAAO;IACT,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC;QACtD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;QACrC,IAAI,CAAC;YACH,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YAClC,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC;YACjD,MAAM,SAAS,CAAC,YAAY,EAAE,CAAC,YAAY,EAAE,UAAU,EAAE,gDAAgD,MAAM,kBAAkB,CAAC,CAAC,CAAC;QACtI,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,CAAC;QACD,OAAO;IACT,CAAC;IACD,MAAM,QAAQ,CAAC,OAAO,EAAE,CAAC,YAAY,EAAE,WAAW,CAAC,EAAE,IAAI,CAAC,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * e2e.config.ts(アプリ側の設定)の型と、それを Playwright の設定に展開するヘルパ。
3
+ *
4
+ * // playwright.config.ts(アプリのルート)
5
+ * import { defineConfig } from "@playwright/test";
6
+ * import { playwrightConfig } from "@ishibashi0112/webview2-bridge-test/config";
7
+ * import e2e from "./e2e/e2e.config";
8
+ * export default defineConfig(playwrightConfig(e2e));
9
+ *
10
+ * 3 つのプロジェクトを作る: screen(L1: Vite dev + MemoryTransport、DB なし)/ api(L2: 実 exe を契約経由で呼ぶ)/
11
+ * host(L3: 実 exe を画面操作)。api / host は会社 PC(Windows)でしか走らない。
12
+ * パス(exe など)は playwright.config.ts のあるディレクトリ(アプリのルート)基準。
13
+ */
14
+ import type { Knex } from "knex";
15
+ import type { PlaywrightTestConfig, PlaywrightTestOptions, PlaywrightWorkerOptions } from "@playwright/test";
16
+ export type Layer = "screen" | "api" | "host";
17
+ export declare const LAYERS: readonly Layer[];
18
+ export interface WebConfig {
19
+ /** dev サーバーの起動コマンド(例 "pnpm --filter web dev")。Playwright の webServer に渡す */
20
+ command: string;
21
+ /** dev サーバーの URL(例 "http://localhost:5173")。screen の baseURL、host の WEBVIEW2_BRIDGE_DEV_URL になる */
22
+ url: string;
23
+ /** 既に起動していれば再利用(既定: CI 以外では true) */
24
+ reuseExistingServer?: boolean | undefined;
25
+ /** command を実行するディレクトリ(既定: アプリのルート) */
26
+ cwd?: string | undefined;
27
+ /** 起動待ちの上限 ms(既定 60000) */
28
+ timeoutMs?: number | undefined;
29
+ }
30
+ export interface HostConfig {
31
+ /** ホスト exe(Debug ビルド)。例 "dotnet/MyApp.Host/bin/Debug/net48/MyApp.Host.exe" */
32
+ exe: string;
33
+ /** WEBVIEW2_BRIDGE_DEV_URL に渡す URL(既定: web.url) */
34
+ devUrl?: string | undefined;
35
+ /** WebView2 の CDP ポート(既定 9222) */
36
+ cdpPort?: number | undefined;
37
+ /** exe 起動から CDP 接続までの上限 ms(既定 30000) */
38
+ startupTimeoutMs?: number | undefined;
39
+ /** exe に渡す追加の環境変数 */
40
+ env?: Record<string, string> | undefined;
41
+ /** テストごとにページを reload して UI の状態を戻す(既定 true) */
42
+ reloadPerTest?: boolean | undefined;
43
+ }
44
+ export type TrackWhere = Record<string, unknown> | ((qb: Knex.QueryBuilder, testId: string) => Knex.QueryBuilder);
45
+ export interface TrackSpec {
46
+ table: string;
47
+ /** 行を同定するキー列 */
48
+ key: string[];
49
+ /**
50
+ * 絞り込み。オブジェクトなら文字列値の "{testId}" を実行 ID に置換する(例 { CustomerCode: "{testId}-C1" })。
51
+ * 関数なら (qb, testId) => qb.where("CustomerCode", "like", `${testId}%`) のように書く
52
+ */
53
+ where?: TrackWhere | undefined;
54
+ }
55
+ export interface DbConfig {
56
+ /**
57
+ * 接続を許可する DB 名(または "host/DB名")。接続先がここに無いとテストは起動時に止まる。
58
+ * 本番 DB に向けて走らせないためのガードなので、テスト DB の名前だけを書く
59
+ */
60
+ allowedDatabases: string[];
61
+ /** 毎テストの前後で自動的に差分を取り、増えた行を後片付けするテーブル */
62
+ track?: TrackSpec[] | undefined;
63
+ /** 接続情報を読む env ファイル(既定 [".env.e2e.local", ".env.e2e"]。アプリのルート基準) */
64
+ envFiles?: string[] | undefined;
65
+ /** snapshot で where を省略したときに読む最大行数(既定 5000) */
66
+ maxRows?: number | undefined;
67
+ }
68
+ export interface ReportConfig {
69
+ /** 出力先(既定 "test-results/report.md") */
70
+ file?: string | undefined;
71
+ /** 失敗があったときクリップボードにコピーする(既定 true。環境変数 E2E_NO_CLIPBOARD=1 でも無効) */
72
+ clipboard?: boolean | undefined;
73
+ /** 文字数の上限(既定 120000。M365 Copilot の貼付上限) */
74
+ maxChars?: number | undefined;
75
+ }
76
+ export interface E2EConfig {
77
+ web: WebConfig;
78
+ host?: HostConfig | undefined;
79
+ db?: DbConfig | undefined;
80
+ /** ブリッジクライアントを公開している window のプロパティ名(既定 "__webview2Bridge") */
81
+ bridgeGlobal?: string | undefined;
82
+ report?: ReportConfig | undefined;
83
+ }
84
+ export declare function defineE2EConfig(config: E2EConfig): E2EConfig;
85
+ /** Playwright の use に足す、このパッケージのワーカーオプション */
86
+ export interface E2EWorkerOptions {
87
+ e2e: E2EConfig;
88
+ layer: Layer;
89
+ }
90
+ /**
91
+ * ブラウザの選び方(screen 用)。
92
+ * - E2E_BROWSER_EXECUTABLE があればその実行ファイル(Chromium をダウンロードできない環境向け)
93
+ * - Windows は既定で Edge(channel "msedge")。E2E_BROWSER_CHANNEL で変えられる("" で Playwright 同梱の Chromium)
94
+ * - それ以外は Playwright 同梱の Chromium(`npx playwright install chromium`)
95
+ */
96
+ export declare function browserUse(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): Partial<PlaywrightWorkerOptions>;
97
+ export type E2EPlaywrightConfig = PlaywrightTestConfig<PlaywrightTestOptions, PlaywrightWorkerOptions & E2EWorkerOptions>;
98
+ export interface PlaywrightConfigOptions {
99
+ /** テストの置き場(既定 "e2e")。配下に screen / api / host */
100
+ testDir?: string | undefined;
101
+ /** 追加・上書きしたい Playwright 設定 */
102
+ overrides?: E2EPlaywrightConfig | undefined;
103
+ }
104
+ /** e2e.config.ts から Playwright の設定を組み立てる */
105
+ export declare function playwrightConfig(e2e: E2EConfig, options?: PlaywrightConfigOptions): E2EPlaywrightConfig;
106
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/config.js ADDED
@@ -0,0 +1,65 @@
1
+ export const LAYERS = ["screen", "api", "host"];
2
+ export function defineE2EConfig(config) {
3
+ return config;
4
+ }
5
+ /**
6
+ * ブラウザの選び方(screen 用)。
7
+ * - E2E_BROWSER_EXECUTABLE があればその実行ファイル(Chromium をダウンロードできない環境向け)
8
+ * - Windows は既定で Edge(channel "msedge")。E2E_BROWSER_CHANNEL で変えられる("" で Playwright 同梱の Chromium)
9
+ * - それ以外は Playwright 同梱の Chromium(`npx playwright install chromium`)
10
+ */
11
+ export function browserUse(env = process.env, platform = process.platform) {
12
+ const executablePath = env["E2E_BROWSER_EXECUTABLE"];
13
+ if (executablePath !== undefined && executablePath !== "") {
14
+ return { browserName: "chromium", launchOptions: { executablePath } };
15
+ }
16
+ const channel = env["E2E_BROWSER_CHANNEL"];
17
+ if (channel !== undefined) {
18
+ return channel === "" ? { browserName: "chromium" } : { browserName: "chromium", channel };
19
+ }
20
+ return platform === "win32" ? { browserName: "chromium", channel: "msedge" } : { browserName: "chromium" };
21
+ }
22
+ /** e2e.config.ts から Playwright の設定を組み立てる */
23
+ export function playwrightConfig(e2e, options = {}) {
24
+ const testDir = options.testDir ?? "e2e";
25
+ const reporterOptions = { ...e2e.report };
26
+ const config = {
27
+ testDir,
28
+ outputDir: "test-results",
29
+ // DB を共有する api / host は直列。screen も揃えて 1 ワーカー(件数が小さいので十分)
30
+ workers: 1,
31
+ fullyParallel: false,
32
+ retries: 0,
33
+ timeout: 60_000,
34
+ expect: { timeout: 10_000 },
35
+ reporter: [["list"], ["@ishibashi0112/webview2-bridge-test/reporter", reporterOptions]],
36
+ use: {
37
+ e2e,
38
+ layer: "screen",
39
+ baseURL: e2e.web.url,
40
+ screenshot: "only-on-failure",
41
+ ...browserUse(),
42
+ },
43
+ webServer: {
44
+ command: e2e.web.command,
45
+ url: e2e.web.url,
46
+ reuseExistingServer: e2e.web.reuseExistingServer ?? process.env["CI"] === undefined,
47
+ timeout: e2e.web.timeoutMs ?? 60_000,
48
+ ...(e2e.web.cwd !== undefined && { cwd: e2e.web.cwd }),
49
+ },
50
+ projects: [
51
+ { name: "screen", testDir: `${testDir}/screen`, use: { layer: "screen" } },
52
+ { name: "api", testDir: `${testDir}/api`, use: { layer: "api" }, timeout: 90_000 },
53
+ { name: "host", testDir: `${testDir}/host`, use: { layer: "host" }, timeout: 90_000 },
54
+ ],
55
+ };
56
+ const o = options.overrides;
57
+ if (o === undefined)
58
+ return config;
59
+ const { use: oUse, projects: oProjects, ...rest } = o;
60
+ const merged = { ...config, ...rest, use: { ...config.use, ...oUse } };
61
+ if (oProjects !== undefined)
62
+ merged.projects = oProjects;
63
+ return merged;
64
+ }
65
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}