enbu 0.4.3 → 0.5.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # enbu
2
2
 
3
- [日本語版 README](./README-ja.md)
3
+ [日本語版 README](https://github.com/9wick/enbu/README-ja.md)
4
4
 
5
5
  > In martial arts, Enbu (演武) is a choreographed demonstration where practitioners perform predefined sequences of techniques. Similarly, Enbu lets you define test sequences in YAML and performs them in the browser — a rehearsal before production.
6
6
 
@@ -52,10 +52,10 @@ steps:
52
52
  - open: https://example.com/login
53
53
  - click: Login
54
54
  - fill:
55
- selector: Email
55
+ text: Email
56
56
  value: user@example.com
57
57
  - fill:
58
- selector: Password
58
+ text: Password
59
59
  value: password123
60
60
  - click: Submit
61
61
  - assertVisible: Dashboard
@@ -84,12 +84,12 @@ steps:
84
84
 
85
85
  ```yaml
86
86
  steps:
87
- # Semantic selector (text, label, ARIA role, etc.)
87
+ # Text selector
88
88
  - click: Login
89
89
 
90
90
  # CSS selector
91
- - click: "#submit-button"
92
- - click: "[data-testid='add-to-cart']"
91
+ - click:
92
+ css: "#submit-button"
93
93
  ```
94
94
 
95
95
  ### Text Input
@@ -98,12 +98,12 @@ steps:
98
98
  steps:
99
99
  # fill: Clear input field then type
100
100
  - fill:
101
- selector: Username
101
+ text: Username
102
102
  value: John Doe
103
103
 
104
104
  # type: Append to existing text
105
105
  - type:
106
- selector: Search box
106
+ text: Search box
107
107
  value: Additional text
108
108
  ```
109
109
 
@@ -136,7 +136,7 @@ steps:
136
136
 
137
137
  # Assert checkbox is not checked
138
138
  - assertChecked:
139
- selector: Optional feature
139
+ text: Optional feature
140
140
  checked: false
141
141
  ```
142
142
 
@@ -153,15 +153,6 @@ steps:
153
153
  full: true
154
154
  ```
155
155
 
156
- ### Snapshot (for debugging)
157
-
158
- ```yaml
159
- steps:
160
- - snapshot: {}
161
- ```
162
-
163
- Captures the accessibility tree of the current page. Useful for debugging element selection.
164
-
165
156
  ### Scroll
166
157
 
167
158
  ```yaml
@@ -184,7 +175,7 @@ steps:
184
175
 
185
176
  # Wait for element to be visible
186
177
  - wait:
187
- selector: "#loading-complete"
178
+ css: "#loading-complete"
188
179
 
189
180
  # Wait for text to appear
190
181
  - wait:
@@ -245,9 +236,11 @@ Each example includes a working Express server and `.enbuflow/` test files. See
245
236
  You can use environment variables in your flows:
246
237
 
247
238
  ```yaml
239
+ env:
240
+ PASSWORD: secret123
248
241
  steps:
249
242
  - fill:
250
- selector: Password
243
+ text: Password
251
244
  value: ${PASSWORD}
252
245
  ```
253
246
 
@@ -265,8 +258,12 @@ npx enbu --env PASSWORD=secret123
265
258
  ```yaml
266
259
  env:
267
260
  BASE_URL: https://staging.example.com
261
+ PASSWORD: secret123
268
262
  steps:
269
263
  - open: ${BASE_URL}/login
264
+ - fill:
265
+ text: Password
266
+ value: ${PASSWORD}
270
267
  ```
271
268
 
272
269
  ## CLI Options
package/dist/main.mjs CHANGED
@@ -6,6 +6,7 @@ import { basename, join, posix, resolve, win32 } from "node:path";
6
6
  import { access, lstat, mkdir, readFile, readdir, readlink, realpath, writeFile } from "node:fs/promises";
7
7
  import * as actualFS from "node:fs";
8
8
  import { constants } from "node:fs";
9
+ import { createHash } from "node:crypto";
9
10
  import { fileURLToPath } from "node:url";
10
11
  import { lstatSync, readdir as readdir$1, readdirSync, readlinkSync, realpathSync } from "fs";
11
12
  import { EventEmitter } from "node:events";
@@ -1142,7 +1143,7 @@ For more information, visit: https://github.com/9wick/enbu
1142
1143
  * フォールバック値として'0.0.0-dev'を使用します。
1143
1144
  */
1144
1145
  const showVersion = () => {
1145
- process.stdout.write(`0.4.3\n`);
1146
+ process.stdout.write(`0.5.0-beta.1\n`);
1146
1147
  };
1147
1148
 
1148
1149
  //#endregion
@@ -14331,7 +14332,7 @@ var require_public_api = /* @__PURE__ */ __commonJSMin(((exports) => {
14331
14332
  }
14332
14333
  return doc;
14333
14334
  }
14334
- function parse(src, reviver, options) {
14335
+ function parse$1(src, reviver, options) {
14335
14336
  let _reviver = void 0;
14336
14337
  if (typeof reviver === "function") _reviver = reviver;
14337
14338
  else if (options === void 0 && reviver && typeof reviver === "object") options = reviver;
@@ -14358,7 +14359,7 @@ var require_public_api = /* @__PURE__ */ __commonJSMin(((exports) => {
14358
14359
  if (identity$1.isDocument(value) && !_replacer) return value.toString(options);
14359
14360
  return new Document$1.Document(value, _replacer, options).toString(options);
14360
14361
  }
14361
- exports.parse = parse;
14362
+ exports.parse = parse$1;
14362
14363
  exports.parseAllDocuments = parseAllDocuments;
14363
14364
  exports.parseDocument = parseDocument;
14364
14365
  exports.stringify = stringify;
@@ -14572,6 +14573,21 @@ function _joinExpects(values$1, separator) {
14572
14573
  return list[0] ?? "never";
14573
14574
  }
14574
14575
  /**
14576
+ * A Valibot error with useful information.
14577
+ */
14578
+ var ValiError = class extends Error {
14579
+ /**
14580
+ * Creates a Valibot error with useful information.
14581
+ *
14582
+ * @param issues The error issues.
14583
+ */
14584
+ constructor(issues) {
14585
+ super(issues[0].message);
14586
+ this.name = "ValiError";
14587
+ this.issues = issues;
14588
+ }
14589
+ };
14590
+ /**
14575
14591
  * Creates a brand transformation action.
14576
14592
  *
14577
14593
  * @param name The brand name.
@@ -15039,6 +15055,20 @@ function unknown() {
15039
15055
  }
15040
15056
  };
15041
15057
  }
15058
+ /**
15059
+ * Parses an unknown input based on a schema.
15060
+ *
15061
+ * @param schema The schema to be used.
15062
+ * @param input The input to be parsed.
15063
+ * @param config The parse configuration.
15064
+ *
15065
+ * @returns The parsed input.
15066
+ */
15067
+ function parse(schema$6, input, config$1) {
15068
+ const dataset = schema$6["~run"]({ value: input }, /* @__PURE__ */ getGlobalConfig(config$1));
15069
+ if (dataset.issues) throw new ValiError(dataset.issues);
15070
+ return dataset.value;
15071
+ }
15042
15072
  /* @__NO_SIDE_EFFECTS__ */
15043
15073
  function pipe(...pipe$1) {
15044
15074
  return {
@@ -15977,6 +16007,11 @@ const resolveEnvVariables = (rawFlow, processEnv, dotEnv) => {
15977
16007
  * - Runtime版スキーマのみを定義(brand/metadata込み)
15978
16008
  * - JSON Schema生成には typeMode: 'input' を使用
15979
16009
  *
16010
+ * 入出力変換:
16011
+ * - YAML入力では `text` キーを使用
16012
+ * - InteractableSelectorSpecSchema: text → interactableText に変換
16013
+ * - AnySelectorSpecSchema: text → anyText に変換
16014
+ *
15980
16015
  * 各セレクタはBranded Typeスキーマを使用し、format検証も行う。
15981
16016
  * これにより1段階で形式検証 + Branded Type化が完了する。
15982
16017
  *
@@ -15993,25 +16028,12 @@ const resolveEnvVariables = (rawFlow, processEnv, dotEnv) => {
15993
16028
  */
15994
16029
  const CssSelectorSchema$1 = object({ css: pipe(CssSelectorSchema, description("Specify element by CSS selector"), metadata({ exampleValues: ["#login-button", ".submit-btn"] })) });
15995
16030
  /**
15996
- * InteractableTextセレクタスキーマ
15997
- *
15998
- * インタラクティブ要素(ボタン、リンク、入力欄等)をテキスト内容で検索する。
15999
- * click, fill, type, hover, select, assertEnabled, assertChecked で使用。
16000
- * 例: { interactableText: "Login" }
16001
- *
16002
- * 出力はBranded Type (InteractableTextSelector)
16003
- */
16004
- const InteractableTextSelectorSchema$1 = object({ interactableText: pipe(InteractableTextSelectorSchema, description("Search for interactive elements by text content"), metadata({ exampleValues: ["Login", "Submit"] })) });
16005
- /**
16006
- * AnyTextセレクタスキーマ
16007
- *
16008
- * 全ての要素(静的テキスト含む)をテキスト内容で検索する。
16009
- * assertVisible, assertNotVisible, scrollIntoView, wait で使用。
16010
- * 例: { anyText: "Welcome" }
16031
+ * テキストセレクタ入力スキーマ(YAML入力用)
16011
16032
  *
16012
- * 出力はBranded Type (AnyTextSelector)
16033
+ * ユーザーは text キーで指定し、内部で用途に応じて
16034
+ * interactableText または anyText に変換される。
16013
16035
  */
16014
- const AnyTextSelectorSchema$1 = object({ anyText: pipe(AnyTextSelectorSchema, description("Search for any elements by text content"), metadata({ exampleValues: ["Welcome", "Error message"] })) });
16036
+ const TextInputSchema = object({ text: pipe(string(), minLength(1, "textセレクタは空文字列にできません"), description("テキスト内容で要素を検索"), metadata({ exampleValues: ["ログイン", "送信"] })) });
16015
16037
  /**
16016
16038
  * XPathセレクタスキーマ
16017
16039
  *
@@ -16024,26 +16046,31 @@ const XpathSelectorSchema$1 = object({ xpath: pipe(XpathSelectorSchema, descript
16024
16046
  /**
16025
16047
  * インタラクティブ要素用セレクタ指定スキーマ
16026
16048
  *
16027
- * css, interactableText, xpath のいずれか1つのみを指定する。
16049
+ * 入力: css, text, xpath のいずれか1つのみを指定する。
16050
+ * text入力の場合、transformで `{ interactableText: ... }` に変換される。
16028
16051
  * click, fill, type, hover, select, assertEnabled, assertChecked で使用。
16029
16052
  */
16030
- const InteractableSelectorSpecSchema = union([
16053
+ const InteractableSelectorSpecSchema = pipe(union([
16031
16054
  CssSelectorSchema$1,
16032
- InteractableTextSelectorSchema$1,
16055
+ TextInputSchema,
16033
16056
  XpathSelectorSchema$1
16034
- ]);
16057
+ ]), transform((input) => M(input).with({ text: z.string }, ({ text }) => {
16058
+ return { interactableText: parse(InteractableTextSelectorSchema, text) };
16059
+ }).with({ css: z._ }, (cssInput) => cssInput).with({ xpath: z._ }, (xpathInput) => xpathInput).exhaustive()));
16035
16060
  /**
16036
16061
  * 全要素用セレクタ指定スキーマ
16037
16062
  *
16038
- * css, anyText, xpath のいずれか1つのみを指定する。
16063
+ * 入力: css, text, xpath のいずれか1つのみを指定する。
16064
+ * text入力の場合、transformで `{ anyText: ... }` に変換される。
16039
16065
  * assertVisible, assertNotVisible, scrollIntoView, wait で使用。
16040
16066
  */
16041
- const AnySelectorSpecSchema = union([
16067
+ const AnySelectorSpecSchema = pipe(union([
16042
16068
  CssSelectorSchema$1,
16043
- AnyTextSelectorSchema$1,
16069
+ TextInputSchema,
16044
16070
  XpathSelectorSchema$1
16045
- ]);
16046
- union([InteractableSelectorSpecSchema, AnySelectorSpecSchema]);
16071
+ ]), transform((input) => M(input).with({ text: z.string }, ({ text }) => {
16072
+ return { anyText: parse(AnyTextSelectorSchema, text) };
16073
+ }).with({ css: z._ }, (cssInput) => cssInput).with({ xpath: z._ }, (xpathInput) => xpathInput).exhaustive()));
16047
16074
  /**
16048
16075
  * Clickコマンドスキーマ定義
16049
16076
  *
@@ -16287,10 +16314,12 @@ const WaitCssSchema = pipe(object({ wait: object({ css: pipe(CssSelectorSchema,
16287
16314
  command: "wait",
16288
16315
  css: input.wait.css
16289
16316
  })));
16290
- const WaitAnyTextSchema = pipe(object({ wait: object({ anyText: pipe(AnyTextSelectorSchema, description("Search for any elements by text content"), metadata({ exampleValues: ["Login", "Submit"] })) }) }), metadata({ description: "Wait until element specified by text is visible" }), transform((input) => ({
16291
- command: "wait",
16292
- anyText: input.wait.anyText
16293
- })));
16317
+ const WaitTextSchema = pipe(object({ wait: object({ text: pipe(string(), minLength(1, "textセレクタは空文字列にできません"), description("テキスト内容で要素を検索"), metadata({ exampleValues: ["ログイン", "送信"] })) }) }), metadata({ description: "Wait until element specified by text is visible" }), transform((input) => {
16318
+ return {
16319
+ command: "wait",
16320
+ anyText: parse(AnyTextSelectorSchema, input.wait.text)
16321
+ };
16322
+ }));
16294
16323
  const WaitXpathSchema = pipe(object({ wait: object({ xpath: pipe(XpathSelectorSchema, description("Specify element by XPath"), metadata({ exampleValues: ["//button[@type='submit']"] })) }) }), metadata({ description: "Wait until element specified by XPath is visible" }), transform((input) => ({
16295
16324
  command: "wait",
16296
16325
  xpath: input.wait.xpath
@@ -16318,7 +16347,7 @@ const WaitFnSchema = pipe(object({ wait: object({ fn: pipe(JsExpressionSchema, d
16318
16347
  const WaitYamlSchema = pipe(union([
16319
16348
  WaitMsSchema,
16320
16349
  WaitCssSchema,
16321
- WaitAnyTextSchema,
16350
+ WaitTextSchema,
16322
16351
  WaitXpathSchema,
16323
16352
  WaitLoadSchema,
16324
16353
  WaitUrlSchema,
@@ -16487,16 +16516,20 @@ const AssertCheckedCssSchema = pipe(object({ assertChecked: object({
16487
16516
  checked: input.assertChecked.checked ?? UseDefault
16488
16517
  })));
16489
16518
  /**
16490
- * assertChecked: { interactableText: "...", checked?: boolean }
16519
+ * assertChecked: { text: "...", checked?: boolean }
16520
+ *
16521
+ * YAML入力形式は text を使用し、内部で interactableText に変換される。
16491
16522
  */
16492
- const AssertCheckedInteractableTextSchema = pipe(object({ assertChecked: object({
16493
- interactableText: pipe(InteractableTextSelectorSchema, description("インタラクティブ要素をテキスト内容で検索"), metadata({ exampleValues: ["Agree to terms", "Enable email notifications"] })),
16523
+ const AssertCheckedTextSchema = pipe(object({ assertChecked: object({
16524
+ text: pipe(string(), minLength(1, "textセレクタは空文字列にできません"), description("テキスト内容で要素を検索"), metadata({ exampleValues: ["同意する", "通知を有効にする"] })),
16494
16525
  checked: CheckedFieldSchema
16495
- }) }), metadata({ description: "テキストで要素を指定してチェック状態を検証" }), transform((input) => ({
16496
- command: "assertChecked",
16497
- interactableText: input.assertChecked.interactableText,
16498
- checked: input.assertChecked.checked ?? UseDefault
16499
- })));
16526
+ }) }), metadata({ description: "テキストで要素を指定してチェック状態を検証" }), transform((input) => {
16527
+ return {
16528
+ command: "assertChecked",
16529
+ interactableText: parse(InteractableTextSelectorSchema, input.assertChecked.text),
16530
+ checked: input.assertChecked.checked ?? UseDefault
16531
+ };
16532
+ }));
16500
16533
  /**
16501
16534
  * assertChecked: { xpath: "...", checked?: boolean }
16502
16535
  */
@@ -16513,10 +16546,12 @@ const AssertCheckedXpathSchema = pipe(object({ assertChecked: object({
16513
16546
  *
16514
16547
  * 3種類のセレクタをunionで組み合わせる。
16515
16548
  * checkedが省略された場合はUseDefaultになる。
16549
+ *
16550
+ * 注意: text キーを入力として受け入れ、内部で interactableText に変換する。
16516
16551
  */
16517
16552
  const AssertCheckedDetailedSchema = union([
16518
16553
  AssertCheckedCssSchema,
16519
- AssertCheckedInteractableTextSchema,
16554
+ AssertCheckedTextSchema,
16520
16555
  AssertCheckedXpathSchema
16521
16556
  ]);
16522
16557
  /**
@@ -16565,16 +16600,20 @@ const TypeCssSchema = pipe(object({ type: object({
16565
16600
  value: input.type.value
16566
16601
  })));
16567
16602
  /**
16568
- * type: { interactableText: "...", value: "..." }
16603
+ * type: { text: "...", value: "..." }
16604
+ *
16605
+ * YAML入力形式は text を使用し、内部で interactableText に変換される。
16569
16606
  */
16570
- const TypeInteractableTextSchema = pipe(object({ type: object({
16571
- interactableText: pipe(InteractableTextSelectorSchema, description("Search for interactive elements by text content"), metadata({ exampleValues: ["Email address", "Username"] })),
16607
+ const TypeTextSchema = pipe(object({ type: object({
16608
+ text: pipe(string(), minLength(1, "textセレクタは空文字列にできません"), description("テキスト内容で要素を検索"), metadata({ exampleValues: ["メールアドレス", "ユーザー名"] })),
16572
16609
  value: TypeValueSchema
16573
- }) }), metadata({ description: "Input to element specified by text" }), transform((input) => ({
16574
- command: "type",
16575
- interactableText: input.type.interactableText,
16576
- value: input.type.value
16577
- })));
16610
+ }) }), metadata({ description: "Input to element specified by text" }), transform((input) => {
16611
+ return {
16612
+ command: "type",
16613
+ interactableText: parse(InteractableTextSelectorSchema, input.type.text),
16614
+ value: input.type.value
16615
+ };
16616
+ }));
16578
16617
  /**
16579
16618
  * type: { xpath: "...", value: "..." }
16580
16619
  */
@@ -16596,7 +16635,7 @@ const TypeXpathSchema = pipe(object({ type: object({
16596
16635
  */
16597
16636
  const TypeYamlSchema = pipe(union([
16598
16637
  TypeCssSchema,
16599
- TypeInteractableTextSchema,
16638
+ TypeTextSchema,
16600
16639
  TypeXpathSchema
16601
16640
  ]), description("要素にテキストを入力する(既存テキストに追加)"), metadata({ category: "Input" }));
16602
16641
  /**
@@ -16615,16 +16654,20 @@ const FillCssSchema = pipe(object({ fill: object({
16615
16654
  value: input.fill.value
16616
16655
  })));
16617
16656
  /**
16618
- * fill: { interactableText: "...", value: "..." }
16657
+ * fill: { text: "...", value: "..." }
16658
+ *
16659
+ * YAML入力形式は text を使用し、内部で interactableText に変換される。
16619
16660
  */
16620
- const FillInteractableTextSchema = pipe(object({ fill: object({
16621
- interactableText: pipe(InteractableTextSelectorSchema, description("Search for interactive elements by text content"), metadata({ exampleValues: ["Email address", "Password"] })),
16661
+ const FillTextSchema = pipe(object({ fill: object({
16662
+ text: pipe(string(), minLength(1, "textセレクタは空文字列にできません"), description("テキスト内容で要素を検索"), metadata({ exampleValues: ["メールアドレス", "パスワード"] })),
16622
16663
  value: FillValueSchema
16623
- }) }), metadata({ description: "Fill element specified by text" }), transform((input) => ({
16624
- command: "fill",
16625
- interactableText: input.fill.interactableText,
16626
- value: input.fill.value
16627
- })));
16664
+ }) }), metadata({ description: "Fill element specified by text" }), transform((input) => {
16665
+ return {
16666
+ command: "fill",
16667
+ interactableText: parse(InteractableTextSelectorSchema, input.fill.text),
16668
+ value: input.fill.value
16669
+ };
16670
+ }));
16628
16671
  /**
16629
16672
  * fill: { xpath: "...", value: "..." }
16630
16673
  */
@@ -16646,7 +16689,7 @@ const FillXpathSchema = pipe(object({ fill: object({
16646
16689
  */
16647
16690
  const FillYamlSchema = pipe(union([
16648
16691
  FillCssSchema,
16649
- FillInteractableTextSchema,
16692
+ FillTextSchema,
16650
16693
  FillXpathSchema
16651
16694
  ]), description("要素にテキストを入力する(既存テキストをクリアして入力)"), metadata({ category: "Input" }));
16652
16695
  /**
@@ -16665,16 +16708,20 @@ const SelectCssSchema = pipe(object({ select: object({
16665
16708
  value: input.select.value
16666
16709
  })));
16667
16710
  /**
16668
- * select: { interactableText: "...", value: "..." }
16711
+ * select: { text: "...", value: "..." }
16712
+ *
16713
+ * YAML入力形式は text を使用し、内部で interactableText に変換される。
16669
16714
  */
16670
- const SelectInteractableTextSchema = pipe(object({ select: object({
16671
- interactableText: pipe(InteractableTextSelectorSchema, description("Search for interactive elements by text content"), metadata({ exampleValues: ["Select country", "Language"] })),
16715
+ const SelectTextSchema = pipe(object({ select: object({
16716
+ text: pipe(string(), minLength(1, "textセレクタは空文字列にできません"), description("テキスト内容で要素を検索"), metadata({ exampleValues: ["国選択", "言語"] })),
16672
16717
  value: SelectValueSchema
16673
- }) }), metadata({ description: "Select option from element specified by text" }), transform((input) => ({
16674
- command: "select",
16675
- interactableText: input.select.interactableText,
16676
- value: input.select.value
16677
- })));
16718
+ }) }), metadata({ description: "Select option from element specified by text" }), transform((input) => {
16719
+ return {
16720
+ command: "select",
16721
+ interactableText: parse(InteractableTextSelectorSchema, input.select.text),
16722
+ value: input.select.value
16723
+ };
16724
+ }));
16678
16725
  /**
16679
16726
  * select: { xpath: "...", value: "..." }
16680
16727
  */
@@ -16696,7 +16743,7 @@ const SelectXpathSchema = pipe(object({ select: object({
16696
16743
  */
16697
16744
  const SelectYamlSchema = pipe(union([
16698
16745
  SelectCssSchema,
16699
- SelectInteractableTextSchema,
16746
+ SelectTextSchema,
16700
16747
  SelectXpathSchema
16701
16748
  ]), description("セレクトボックスからオプションを選択する"), metadata({ category: "Input" }));
16702
16749
  /**
@@ -17160,13 +17207,18 @@ const buildRawFlowData = (root) => ({
17160
17207
  });
17161
17208
  /**
17162
17209
  * 解決済みRawFlowDataからFlowを構築する
17210
+ *
17211
+ * @param resolvedRawFlow - 環境変数解決済みのRawFlowData
17212
+ * @param filePath - フローファイルの絶対パス
17213
+ * @returns 成功時: Flowオブジェクト、失敗時: ParseError
17163
17214
  */
17164
- const buildFlow = (resolvedRawFlow, fileName) => validateCommands(resolvedRawFlow.steps).map((validatedCommands) => ({
17165
- name: fileName.replace(/\.enbu\.yaml$/, ""),
17215
+ const buildFlow = (resolvedRawFlow, filePath) => validateCommands(resolvedRawFlow.steps).map((validatedCommands) => ({
17216
+ name: path.basename(filePath).replace(/\.enbu\.yaml$/, ""),
17166
17217
  env: resolvedRawFlow.env,
17167
- steps: validatedCommands
17218
+ steps: validatedCommands,
17219
+ filePath
17168
17220
  }));
17169
- const parseFlowYaml = (yamlContent, fileName, processEnv, dotEnv) => safeYamlParse(yamlContent).andThen(validateRootStructure).map(buildRawFlowData).andThen((rawFlow) => resolveEnvVariables(rawFlow, processEnv, dotEnv)).andThen((resolvedRawFlow) => buildFlow(resolvedRawFlow, fileName));
17221
+ const parseFlowYaml = (yamlContent, filePath, processEnv, dotEnv) => safeYamlParse(yamlContent).andThen(validateRootStructure).map(buildRawFlowData).andThen((rawFlow) => resolveEnvVariables(rawFlow, processEnv, dotEnv)).andThen((resolvedRawFlow) => buildFlow(resolvedRawFlow, filePath));
17170
17222
  /**
17171
17223
  * SelectorSpecまたはResolvedSelectorSpecからセレクタ文字列を取得する
17172
17224
  *
@@ -18696,6 +18748,25 @@ const executeFlow = (flow, options) => {
18696
18748
  * eslint.config.mjsで除外設定済み。
18697
18749
  */
18698
18750
  /**
18751
+ * ファイルパスからセッション名を生成する
18752
+ *
18753
+ * ファイルパスに基づいて一意のセッション名を生成する。
18754
+ * SHA-256ハッシュの先頭6文字を使用し、衝突確率を低く抑えながら
18755
+ * 識別しやすい長さのセッション名を生成する。
18756
+ *
18757
+ * @param filePath - フローファイルの絶対パス
18758
+ * @returns `enbu-{flowName}-{hash}` 形式のセッション名
18759
+ *
18760
+ * @example
18761
+ * ```typescript
18762
+ * generateSessionNameFromPath('/path/to/login.enbu.yaml')
18763
+ * // => 'enbu-login-a1b2c3'
18764
+ * ```
18765
+ */
18766
+ const generateSessionNameFromPath = (filePath) => {
18767
+ return `enbu-${basename(filePath).replace(/\.enbu\.yaml$/, "")}-${createHash("sha256").update(filePath).digest("hex").slice(0, 6)}`;
18768
+ };
18769
+ /**
18699
18770
  * ScreenshotResultからパスを取得する
18700
18771
  *
18701
18772
  * スクリーンショットが撮影成功した場合はパスを返し、
@@ -18744,12 +18815,11 @@ const discoverFlowFiles = (files, cwd) => {
18744
18815
  * @returns 成功時: Flowオブジェクト、失敗時: OrchestratorError
18745
18816
  */
18746
18817
  const loadSingleFlow = (filePath, processEnv, inputEnv) => {
18747
- const fileName = basename(filePath);
18748
18818
  return import_index_cjs$1.ResultAsync.fromPromise(readFile(filePath, "utf-8"), (error) => ({
18749
18819
  type: "load_error",
18750
18820
  message: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`,
18751
18821
  filePath
18752
- })).andThen((yamlContent) => parseFlowYaml(yamlContent, fileName, processEnv, inputEnv).mapErr((error) => ({
18822
+ })).andThen((yamlContent) => parseFlowYaml(yamlContent, filePath, processEnv, inputEnv).mapErr((error) => ({
18753
18823
  type: "load_error",
18754
18824
  message: formatParseErrorMessage(error),
18755
18825
  filePath
@@ -18784,16 +18854,20 @@ const loadAllFlows = (flowFiles, processEnv, inputEnv) => {
18784
18854
  /**
18785
18855
  * FlowExecutionOptionsを構築する
18786
18856
  *
18857
+ * @param flow - 実行対象のフロー
18787
18858
  * @param input - 実行オプション
18788
18859
  * @param cwd - 作業ディレクトリ
18789
18860
  * @returns FlowExecutionOptions
18790
18861
  */
18791
- const buildFlowExecutionOptions = (input, cwd) => {
18862
+ const buildFlowExecutionOptions = (flow, input, cwd) => {
18792
18863
  return {
18793
18864
  session: input.sessionName !== void 0 ? {
18794
18865
  type: "name",
18795
18866
  value: input.sessionName
18796
- } : { type: "default" },
18867
+ } : {
18868
+ type: "name",
18869
+ value: generateSessionNameFromPath(flow.filePath)
18870
+ },
18797
18871
  headed: input.headed ?? DEFAULT_OPTIONS.headed,
18798
18872
  env: input.env ?? DEFAULT_OPTIONS.env,
18799
18873
  commandTimeoutMs: input.commandTimeoutMs ?? DEFAULT_OPTIONS.commandTimeoutMs,
@@ -18838,7 +18912,7 @@ const notifyFlowProgress = async (input, flowName, status, duration, stepTotal)
18838
18912
  const executeSingleFlow = async (flow, input) => {
18839
18913
  const startTime = Date.now();
18840
18914
  await notifyFlowProgress(input, flow.name, "start", 0, flow.steps.length);
18841
- return (await executeFlow(flow, buildFlowExecutionOptions(input, input.cwd))).match(async (flowResult) => {
18915
+ return (await executeFlow(flow, buildFlowExecutionOptions(flow, input, input.cwd))).match(async (flowResult) => {
18842
18916
  const duration = Date.now() - startTime;
18843
18917
  const status = flowResult.status;
18844
18918
  await notifyFlowProgress(input, flow.name, status, duration, void 0);