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 +17 -20
- package/dist/main.mjs +153 -79
- package/dist/main.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# enbu
|
|
2
2
|
|
|
3
|
-
[日本語版 README](
|
|
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
|
-
|
|
55
|
+
text: Email
|
|
56
56
|
value: user@example.com
|
|
57
57
|
- fill:
|
|
58
|
-
|
|
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
|
-
#
|
|
87
|
+
# Text selector
|
|
88
88
|
- click: Login
|
|
89
89
|
|
|
90
90
|
# CSS selector
|
|
91
|
-
- click:
|
|
92
|
-
|
|
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
|
-
|
|
101
|
+
text: Username
|
|
102
102
|
value: John Doe
|
|
103
103
|
|
|
104
104
|
# type: Append to existing text
|
|
105
105
|
- type:
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
*
|
|
16033
|
+
* ユーザーは text キーで指定し、内部で用途に応じて
|
|
16034
|
+
* interactableText または anyText に変換される。
|
|
16013
16035
|
*/
|
|
16014
|
-
const
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
16069
|
+
TextInputSchema,
|
|
16044
16070
|
XpathSelectorSchema$1
|
|
16045
|
-
])
|
|
16046
|
-
|
|
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
|
|
16291
|
-
|
|
16292
|
-
|
|
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
|
-
|
|
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: {
|
|
16519
|
+
* assertChecked: { text: "...", checked?: boolean }
|
|
16520
|
+
*
|
|
16521
|
+
* YAML入力形式は text を使用し、内部で interactableText に変換される。
|
|
16491
16522
|
*/
|
|
16492
|
-
const
|
|
16493
|
-
|
|
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
|
-
|
|
16497
|
-
|
|
16498
|
-
|
|
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
|
-
|
|
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: {
|
|
16603
|
+
* type: { text: "...", value: "..." }
|
|
16604
|
+
*
|
|
16605
|
+
* YAML入力形式は text を使用し、内部で interactableText に変換される。
|
|
16569
16606
|
*/
|
|
16570
|
-
const
|
|
16571
|
-
|
|
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
|
-
|
|
16575
|
-
|
|
16576
|
-
|
|
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
|
-
|
|
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: {
|
|
16657
|
+
* fill: { text: "...", value: "..." }
|
|
16658
|
+
*
|
|
16659
|
+
* YAML入力形式は text を使用し、内部で interactableText に変換される。
|
|
16619
16660
|
*/
|
|
16620
|
-
const
|
|
16621
|
-
|
|
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
|
-
|
|
16625
|
-
|
|
16626
|
-
|
|
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
|
-
|
|
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: {
|
|
16711
|
+
* select: { text: "...", value: "..." }
|
|
16712
|
+
*
|
|
16713
|
+
* YAML入力形式は text を使用し、内部で interactableText に変換される。
|
|
16669
16714
|
*/
|
|
16670
|
-
const
|
|
16671
|
-
|
|
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
|
-
|
|
16675
|
-
|
|
16676
|
-
|
|
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
|
-
|
|
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,
|
|
17165
|
-
name:
|
|
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,
|
|
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,
|
|
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
|
-
} : {
|
|
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);
|