@quiesco/steps 0.0.0-stage → 0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Quiesco contributors
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
@@ -1,3 +1,95 @@
1
- # Temporary Holding Version
1
+ # @quiesco/steps
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Optional generic cucumber steps for Quiesco, in Chinese, Japanese and English.
4
+
5
+ > **Operation scripts, not requirements.** These steps describe taps and texts ("tap `login-button`"),
6
+ > not what the user wants to achieve. Use them while prototyping or exploring an app. A real project
7
+ > writes its own steps in the language of the business ("When the user logs in as …") on top of
8
+ > `this.app` — or a driver of its own (see the [driver pattern](https://shift-14cc7601.mintlify.site/driver-pattern)) — so the features
9
+ > keep describing requirements when the UI changes.
10
+
11
+ ## Use
12
+
13
+ ```ts
14
+ // features/support/steps.ts
15
+ import { registerQuiescoSteps } from '@quiesco/steps';
16
+
17
+ registerQuiescoSteps(); // all languages; or { languages: ['ja'] }, { only: ['tap', 'seeText'] }
18
+ ```
19
+
20
+ `@quiesco/cucumber/register` must be imported too (it provides `this.app`). The steps are registered
21
+ with cucumber's keyword-agnostic `defineStep`, so they work after any keyword (`当` / `もし` / `When` …)
22
+ and in feature files of any `# language:`.
23
+
24
+ A quoted target is a **testID**; the "text" variants match the displayed text instead. Every step waits
25
+ for the app to become idle like any other Quiesco call — no sleeps.
26
+
27
+ ## Steps
28
+
29
+ | Step | 中文 | 日本語 | English |
30
+ |---|---|---|---|
31
+ | `tap` | `点击 "add-todo"` | `"add-todo" をタップする` | `I tap "add-todo"` |
32
+ | `tapText` | `点击文本 "保存"` | `テキスト "保存" をタップする` | `I tap the text "Save"` |
33
+ | `longPress` | `长按 "todo-item"` | `"todo-item" を長押しする` | `I long press "todo-item"` |
34
+ | `type` | `在 "todo-title" 中输入 "买牛奶"` | `"todo-title" に "牛乳を買う" と入力する` | `I type "Buy milk" into "todo-title"` |
35
+ | `clear` | `清空 "todo-title" 的内容` | `"todo-title" の入力をクリアする` | `I clear "todo-title"` |
36
+ | `scrollTo` | `在 "todo-list" 中滚动到 "todo-item-30"` | `"todo-list" を "todo-item-30" までスクロールする` | `I scroll "todo-list" to "todo-item-30"` |
37
+ | `dismissKeyboard` | `收起键盘` | `キーボードを閉じる` | `I dismiss the keyboard` |
38
+ | `pressBack` | `按返回键` | `戻るボタンを押す` | `I press back` |
39
+ | `openUrl` | `打开链接 "myapp://todos"` | `リンク "myapp://todos" を開く` | `I open the link "myapp://todos"` |
40
+ | `seeText` | `看到文本 "买牛奶"` | `テキスト "牛乳を買う" が表示されている` | `I see the text "Buy milk"` |
41
+ | `notSeeText` | `看不到文本 "买牛奶"` | `テキスト "牛乳を買う" が表示されていない` | `I do not see the text "Buy milk"` |
42
+ | `visible` | `元素 "todo-list" 可见` | `要素 "todo-list" が表示されている` | `the element "todo-list" is visible` |
43
+ | `notVisible` | `元素 "todo-title-error" 不可见` | `要素 "todo-title-error" が表示されていない` | `the element "todo-title-error" is not visible` |
44
+ | `hasText` | `元素 "greeting" 的文本是 "a@example.com"` | `要素 "greeting" のテキストが "a@example.com" である` | `the element "greeting" has the text "a@example.com"` |
45
+ | `count` | `元素 "todo-item" 共有 2 个` | `要素 "todo-item" が 2 個ある` | `there are 2 elements "todo-item"` |
46
+ | `doubleTap` | `双击 "photo"` | `"photo" をダブルタップする` | `I double tap "photo"` |
47
+ | `replaceText` | `把 "todo-title" 的内容替换为 "买面包"` | `"todo-title" の入力を "パンを買う" に置き換える` | `I replace the text of "todo-title" with "Buy bread"` |
48
+ | `pressEnter` | `在 "todo-title" 中按回车` | `"todo-title" でエンターキーを押す` | `I press enter in "todo-title"` |
49
+ | `scroll` | `在 "todo-list" 中向下滚动` | `"todo-list" を下にスクロールする` | `I scroll "todo-list" down` |
50
+ | `scrollToEdge` | `把 "todo-list" 滚动到底部` | `"todo-list" を一番下までスクロールする` | `I scroll "todo-list" to the bottom` |
51
+ | `swipe` | `在 "todo-item" 上向左滑动` | `"todo-item" を左にスワイプする` | `I swipe left on "todo-item"` |
52
+ | `drag` | `把 "item-1" 拖到 "item-3"` | `"item-1" を "item-3" までドラッグする` | `I drag "item-1" to "item-3"` |
53
+ | `pinch` | `在 "photo" 上双指缩放到 2 倍` | `"photo" を 2 倍にピンチする` | `I pinch "photo" to 2 times` |
54
+ | `setSlider` | `把滑块 "volume" 设为 30` | `スライダー "volume" を 30 にする` | `I set the slider "volume" to 30` |
55
+ | `switchOn` | `打开开关 "notify"` | `スイッチ "notify" をオンにする` | `I turn on the switch "notify"` |
56
+ | `switchOff` | `关闭开关 "notify"` | `スイッチ "notify" をオフにする` | `I turn off the switch "notify"` |
57
+ | `select` | `在 "priority" 中选择 "高"` | `"priority" で "高" を選ぶ` | `I select "High" in "priority"` |
58
+ | `enabled` | `元素 "save" 可用` | `要素 "save" が有効である` | `the element "save" is enabled` |
59
+ | `disabled` | `元素 "save" 不可用` | `要素 "save" が無効である` | `the element "save" is disabled` |
60
+ | `checked` | `元素 "notify" 已选中` | `要素 "notify" がオンである` | `the element "notify" is checked` |
61
+ | `notChecked` | `元素 "notify" 未选中` | `要素 "notify" がオフである` | `the element "notify" is not checked` |
62
+ | `focused` | `元素 "todo-title" 获得焦点` | `要素 "todo-title" にフォーカスがある` | `the element "todo-title" has the focus` |
63
+ | `hasValue` | `元素 "priority" 的值是 "high"` | `要素 "priority" の値が "high" である` | `the element "priority" has the value "high"` |
64
+ | `sliderValue` | `滑块 "volume" 的值是 30` | `スライダー "volume" の値が 30 である` | `the slider "volume" has the value 30` |
65
+ | `background` | `把 App 切到后台` | `アプリをバックグラウンドにする` | `I send the app to the background` |
66
+ | `foreground` | `把 App 切回前台` | `アプリをフォアグラウンドに戻す` | `I bring the app to the foreground` |
67
+ | `relaunch` | `重新启动 App` | `アプリを再起動する` | `I relaunch the app` |
68
+ | `orientation` | `把设备转到横屏` | `画面を横向きにする` | `I rotate the device to landscape` |
69
+ | `appearance` | `把外观设为深色模式` | `外観をダークモードにする` | `I switch to dark mode` |
70
+ | `location` | `把设备位置设为 35.68, 139.76` | `端末の位置を 35.68, 139.76 にする` | `I set the device location to 35.68, 139.76` |
71
+ | `offline` | `把设备切换到离线` | `端末をオフラインにする` | `I go offline` |
72
+ | `online` | `把设备切换到在线` | `端末をオンラインにする` | `I go online` |
73
+ | `grantPermission` | `授予 App 定位权限` | `アプリに位置情報の権限を与える` | `I grant the location permission` |
74
+ | `revokePermission` | `撤销 App 的定位权限` | `アプリの位置情報の権限を取り消す` | `I revoke the location permission` |
75
+ | `allowDialog` | `在系统弹窗中允许` | `システムダイアログで許可する` | `I allow in the system dialog` |
76
+ | `denyDialog` | `在系统弹窗中拒绝` | `システムダイアログで拒否する` | `I deny in the system dialog` |
77
+ | `shake` | `摇动设备` | `端末を振る` | `I shake the device` |
78
+ | `screenshot` | `截图并保存为 "列表"` | `スクリーンショットを "一覧" として保存する` | `I take a screenshot named "list"` |
79
+
80
+ `pressBack` is not available on iOS (no back button); Quiesco reports `ACTION_NOT_SUPPORTED` there.
81
+
82
+ Some steps take a word instead of a quoted value (any of the languages' words works in every language):
83
+
84
+ | Word | Values |
85
+ |---|---|
86
+ | Direction (`scroll`, `swipe`: the finger's direction for a swipe) | 上 下 左 右 / up down left right |
87
+ | Edge (`scrollToEdge`) | 顶部 底部 最左边 最右边 / 一番上 一番下 左端 右端 / top bottom left right |
88
+ | Orientation | 竖屏 横屏 / 縦向き 横向き / portrait landscape |
89
+ | Appearance | 浅色 深色 / ライト ダーク / light dark |
90
+ | Permission | 相机 麦克风 定位 通知 通讯录 照片 日历 / カメラ マイク 位置情報 通知 連絡先 写真 カレンダー / camera microphone location notifications contacts photos calendar |
91
+
92
+ An unknown word fails the step with the list of accepted ones. Device and system steps (`background`,
93
+ `orientation`, `offline`, `grantPermission`, `allowDialog`, `shake`, …) call `this.app.device`; where a
94
+ platform cannot do something Quiesco reports `ACTION_NOT_SUPPORTED` with the reason
95
+ ([platform differences](https://shift-14cc7601.mintlify.site/platforms)).
@@ -0,0 +1,16 @@
1
+ import type { QuiescoWorld } from '@quiesco/cucumber';
2
+ import type { Language } from './steps.js';
3
+ export { GENERIC_STEPS, argsFor } from './steps.js';
4
+ export type { GenericStep, Language } from './steps.js';
5
+ export interface RegisterStepsOptions {
6
+ /** Step languages to register. Default: all (zh, ja, en). */
7
+ languages?: readonly Language[];
8
+ /** Only these steps (by `name`). Default: all. */
9
+ only?: readonly string[];
10
+ }
11
+ type Define = (expression: string, fn: (this: QuiescoWorld, ...params: (string | number)[]) => Promise<void>) => void;
12
+ /** Number of `{…}` parameters in a cucumber expression. */
13
+ export declare function parameterCount(expression: string): number;
14
+ /** Register the generic steps with cucumber-js. Returns the expressions it registered. */
15
+ export declare function registerQuiescoSteps(options?: RegisterStepsOptions, define?: Define): string[];
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,KAAK,EAAe,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGxD,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACpD,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAExD,MAAM,WAAW,oBAAoB;IACnC,6DAA6D;IAC7D,SAAS,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IAChC,kDAAkD;IAClD,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B;AAED,KAAK,MAAM,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,MAAM,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;AAEtH,2DAA2D;AAC3D,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEzD;AA2BD,0FAA0F;AAC1F,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,oBAAyB,EAAE,MAAM,GAAE,MAAwC,GAAG,MAAM,EAAE,CAcnI"}
package/dist/index.js ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @quiesco/steps — optional generic steps (requirements.md §6.11).
3
+ *
4
+ * // features/support/steps.ts
5
+ * import { registerQuiescoSteps } from '@quiesco/steps';
6
+ * registerQuiescoSteps({ languages: ['zh'] });
7
+ *
8
+ * Only for prototyping and exploration: these steps script taps and texts instead of stating what the
9
+ * user wants. Real projects write their own business-language steps (see docs/driver-pattern.mdx).
10
+ */
11
+ import { defineStep } from '@cucumber/cucumber';
12
+ import { GENERIC_STEPS, argsFor } from './steps.js';
13
+ export { GENERIC_STEPS, argsFor } from './steps.js';
14
+ /** Number of `{…}` parameters in a cucumber expression. */
15
+ export function parameterCount(expression) {
16
+ return (expression.match(/\{[^}]*\}/g) ?? []).length;
17
+ }
18
+ /**
19
+ * cucumber-js checks a step function's arity against the expression's parameters, so the wrapper must
20
+ * declare exactly that many (a rest parameter has length 0).
21
+ */
22
+ function withArity(count, body) {
23
+ switch (count) {
24
+ case 0:
25
+ return async function () {
26
+ await body(this, []);
27
+ };
28
+ case 1:
29
+ return async function (a) {
30
+ await body(this, [a]);
31
+ };
32
+ case 2:
33
+ return async function (a, b) {
34
+ await body(this, [a, b]);
35
+ };
36
+ default:
37
+ throw new Error(`generic steps take at most 2 parameters (got ${count})`);
38
+ }
39
+ }
40
+ /** Register the generic steps with cucumber-js. Returns the expressions it registered. */
41
+ export function registerQuiescoSteps(options = {}, define = defineStep) {
42
+ const languages = options.languages ?? ['zh', 'ja', 'en'];
43
+ const steps = options.only === undefined ? GENERIC_STEPS : GENERIC_STEPS.filter((st) => options.only?.includes(st.name));
44
+ const registered = [];
45
+ for (const step of steps) {
46
+ for (const language of languages) {
47
+ const expression = step.expressions[language];
48
+ define(expression, withArity(parameterCount(expression), async (world, params) => {
49
+ await step.run(world.app, ...argsFor(step, language, params));
50
+ }));
51
+ registered.push(expression);
52
+ }
53
+ }
54
+ return registered;
55
+ }
56
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAIhD,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAYpD,2DAA2D;AAC3D,MAAM,UAAU,cAAc,CAAC,UAAkB;IAC/C,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;AACvD,CAAC;AAID;;;GAGG;AACH,SAAS,SAAS,CAAC,KAAa,EAAE,IAAU;IAC1C,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,CAAC;YACJ,OAAO,KAAK;gBACV,MAAM,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACvB,CAAC,CAAC;QACJ,KAAK,CAAC;YACJ,OAAO,KAAK,WAA+B,CAAkB;gBAC3D,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YACxB,CAAC,CAAC;QACJ,KAAK,CAAC;YACJ,OAAO,KAAK,WAA+B,CAAkB,EAAE,CAAkB;gBAC/E,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAC3B,CAAC,CAAC;QACJ;YACE,MAAM,IAAI,KAAK,CAAC,gDAAgD,KAAK,GAAG,CAAC,CAAC;IAC9E,CAAC;AACH,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,oBAAoB,CAAC,UAAgC,EAAE,EAAE,SAAiB,UAA+B;IACvH,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAW,CAAC;IACrE,MAAM,KAAK,GAA2B,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IACjJ,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;YAC9C,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;gBAC/E,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;YAChE,CAAC,CAAC,CAAC,CAAC;YACJ,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The generic steps (requirements.md §6.11), in Chinese, Japanese and English.
3
+ *
4
+ * These are operation scripts, not requirements: they describe taps and texts, not what the user wants
5
+ * to achieve. Use them while prototyping or exploring; a real project writes business-language steps
6
+ * ("When the user logs in as …") on top of `this.app` or its own driver.
7
+ *
8
+ * A quoted target is a testID (`点击 "add-todo"`); the "…文本 / テキスト / the text …" variants match
9
+ * the displayed text instead.
10
+ */
11
+ import type { Driver } from '@quiesco/core';
12
+ export type Language = 'zh' | 'ja' | 'en';
13
+ export interface GenericStep {
14
+ /** Stable name, e.g. for docs. */
15
+ name: string;
16
+ /** Cucumber expression per language. Parameters are passed to `run` in the order they appear. */
17
+ expressions: Record<Language, string>;
18
+ /** Example step text per language (docs and tests). */
19
+ examples: Record<Language, string>;
20
+ /** Maps the language's parameters to `run` arguments when their order differs from `zh`. */
21
+ order?: Partial<Record<Language, number[]>>;
22
+ run(app: Driver, ...args: (string | number)[]): Promise<unknown>;
23
+ }
24
+ export declare const GENERIC_STEPS: readonly GenericStep[];
25
+ /** Arguments as `run` expects them, from the parameters in the order the language's expression has them. */
26
+ export declare function argsFor(step: GenericStep, language: Language, params: readonly (string | number)[]): (string | number)[];
27
+ //# sourceMappingURL=steps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"steps.d.ts","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE1C,MAAM,WAAW,WAAW;IAC1B,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,iGAAiG;IACjG,WAAW,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACtC,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACnC,4FAA4F;IAC5F,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAC5C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAClE;AA6BD,eAAO,MAAM,aAAa,EAAE,SAAS,WAAW,EA+S/C,CAAC;AACF,4GAA4G;AAC5G,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAGxH"}
package/dist/steps.js ADDED
@@ -0,0 +1,332 @@
1
+ const s = (v) => String(v);
2
+ /** A word from the step text mapped to an API value; the same table serves every language. */
3
+ function word(kind, table, v) {
4
+ const key = s(v).trim().toLowerCase();
5
+ const hit = table[key];
6
+ if (hit === undefined)
7
+ throw new Error(`Unknown ${kind} "${s(v)}": use one of ${Object.keys(table).join(', ')}`);
8
+ return hit;
9
+ }
10
+ const DIRECTIONS = { 上: 'up', 下: 'down', 左: 'left', 右: 'right', up: 'up', down: 'down', left: 'left', right: 'right' };
11
+ const EDGES = {
12
+ 顶部: 'top', 底部: 'bottom', 最左边: 'left', 最右边: 'right',
13
+ 一番上: 'top', 一番下: 'bottom', 左端: 'left', 右端: 'right',
14
+ top: 'top', bottom: 'bottom', left: 'left', right: 'right',
15
+ };
16
+ const ORIENTATIONS = { 竖屏: 'portrait', 横屏: 'landscape', 縦向き: 'portrait', 横向き: 'landscape', portrait: 'portrait', landscape: 'landscape' };
17
+ const APPEARANCES = { 浅色: 'light', 深色: 'dark', ライト: 'light', ダーク: 'dark', light: 'light', dark: 'dark' };
18
+ const PERMISSIONS = {
19
+ 相机: 'camera', 麦克风: 'microphone', 定位: 'location', 通知: 'notifications', 通讯录: 'contacts', 照片: 'photos', 日历: 'calendar',
20
+ カメラ: 'camera', マイク: 'microphone', 位置情報: 'location', 連絡先: 'contacts', 写真: 'photos', カレンダー: 'calendar',
21
+ camera: 'camera', microphone: 'microphone', location: 'location', notifications: 'notifications', contacts: 'contacts', photos: 'photos', calendar: 'calendar',
22
+ };
23
+ export const GENERIC_STEPS = [
24
+ {
25
+ name: 'tap',
26
+ expressions: { zh: '点击 {string}', ja: '{string} をタップする', en: 'I tap {string}' },
27
+ examples: { zh: '点击 "add-todo"', ja: '"add-todo" をタップする', en: 'I tap "add-todo"' },
28
+ run: (app, id) => app.tap(s(id)),
29
+ },
30
+ {
31
+ name: 'tapText',
32
+ expressions: { zh: '点击文本 {string}', ja: 'テキスト {string} をタップする', en: 'I tap the text {string}' },
33
+ examples: { zh: '点击文本 "保存"', ja: 'テキスト "保存" をタップする', en: 'I tap the text "Save"' },
34
+ run: (app, text) => app.tap({ text: s(text) }),
35
+ },
36
+ {
37
+ name: 'longPress',
38
+ expressions: { zh: '长按 {string}', ja: '{string} を長押しする', en: 'I long press {string}' },
39
+ examples: { zh: '长按 "todo-item"', ja: '"todo-item" を長押しする', en: 'I long press "todo-item"' },
40
+ run: (app, id) => app.longPress(s(id)),
41
+ },
42
+ {
43
+ name: 'type',
44
+ expressions: { zh: '在 {string} 中输入 {string}', ja: '{string} に {string} と入力する', en: 'I type {string} into {string}' },
45
+ examples: { zh: '在 "todo-title" 中输入 "买牛奶"', ja: '"todo-title" に "牛乳を買う" と入力する', en: 'I type "Buy milk" into "todo-title"' },
46
+ // en names the text first: run(target, text)
47
+ order: { en: [1, 0] },
48
+ run: (app, id, text) => app.type(s(id), s(text)),
49
+ },
50
+ {
51
+ name: 'clear',
52
+ expressions: { zh: '清空 {string} 的内容', ja: '{string} の入力をクリアする', en: 'I clear {string}' },
53
+ examples: { zh: '清空 "todo-title" 的内容', ja: '"todo-title" の入力をクリアする', en: 'I clear "todo-title"' },
54
+ run: (app, id) => app.clear(s(id)),
55
+ },
56
+ {
57
+ name: 'scrollTo',
58
+ expressions: { zh: '在 {string} 中滚动到 {string}', ja: '{string} を {string} までスクロールする', en: 'I scroll {string} to {string}' },
59
+ examples: { zh: '在 "todo-list" 中滚动到 "todo-item-30"', ja: '"todo-list" を "todo-item-30" までスクロールする', en: 'I scroll "todo-list" to "todo-item-30"' },
60
+ run: (app, container, target) => app.scrollTo(s(container), s(target)),
61
+ },
62
+ {
63
+ name: 'dismissKeyboard',
64
+ expressions: { zh: '收起键盘', ja: 'キーボードを閉じる', en: 'I dismiss the keyboard' },
65
+ examples: { zh: '收起键盘', ja: 'キーボードを閉じる', en: 'I dismiss the keyboard' },
66
+ run: (app) => app.dismissKeyboard(),
67
+ },
68
+ {
69
+ name: 'pressBack',
70
+ expressions: { zh: '按返回键', ja: '戻るボタンを押す', en: 'I press back' },
71
+ examples: { zh: '按返回键', ja: '戻るボタンを押す', en: 'I press back' },
72
+ run: (app) => app.pressBack(),
73
+ },
74
+ {
75
+ name: 'openUrl',
76
+ expressions: { zh: '打开链接 {string}', ja: 'リンク {string} を開く', en: 'I open the link {string}' },
77
+ examples: { zh: '打开链接 "myapp://todos"', ja: 'リンク "myapp://todos" を開く', en: 'I open the link "myapp://todos"' },
78
+ run: (app, url) => app.openUrl(s(url)),
79
+ },
80
+ {
81
+ name: 'seeText',
82
+ expressions: { zh: '看到文本 {string}', ja: 'テキスト {string} が表示されている', en: 'I see the text {string}' },
83
+ examples: { zh: '看到文本 "买牛奶"', ja: 'テキスト "牛乳を買う" が表示されている', en: 'I see the text "Buy milk"' },
84
+ run: (app, text) => app.expectVisible({ text: s(text) }),
85
+ },
86
+ {
87
+ name: 'notSeeText',
88
+ expressions: { zh: '看不到文本 {string}', ja: 'テキスト {string} が表示されていない', en: 'I do not see the text {string}' },
89
+ examples: { zh: '看不到文本 "买牛奶"', ja: 'テキスト "牛乳を買う" が表示されていない', en: 'I do not see the text "Buy milk"' },
90
+ run: (app, text) => app.expectNotVisible({ text: s(text) }),
91
+ },
92
+ {
93
+ name: 'visible',
94
+ expressions: { zh: '元素 {string} 可见', ja: '要素 {string} が表示されている', en: 'the element {string} is visible' },
95
+ examples: { zh: '元素 "todo-list" 可见', ja: '要素 "todo-list" が表示されている', en: 'the element "todo-list" is visible' },
96
+ run: (app, id) => app.expectVisible(s(id)),
97
+ },
98
+ {
99
+ name: 'notVisible',
100
+ expressions: { zh: '元素 {string} 不可见', ja: '要素 {string} が表示されていない', en: 'the element {string} is not visible' },
101
+ examples: { zh: '元素 "todo-title-error" 不可见', ja: '要素 "todo-title-error" が表示されていない', en: 'the element "todo-title-error" is not visible' },
102
+ run: (app, id) => app.expectNotVisible(s(id)),
103
+ },
104
+ {
105
+ name: 'hasText',
106
+ expressions: { zh: '元素 {string} 的文本是 {string}', ja: '要素 {string} のテキストが {string} である', en: 'the element {string} has the text {string}' },
107
+ examples: { zh: '元素 "greeting" 的文本是 "a@example.com"', ja: '要素 "greeting" のテキストが "a@example.com" である', en: 'the element "greeting" has the text "a@example.com"' },
108
+ run: (app, id, text) => app.expectText(s(id), s(text)),
109
+ },
110
+ {
111
+ name: 'count',
112
+ expressions: { zh: '元素 {string} 共有 {int} 个', ja: '要素 {string} が {int} 個ある', en: 'there are {int} elements {string}' },
113
+ examples: { zh: '元素 "todo-item" 共有 2 个', ja: '要素 "todo-item" が 2 個ある', en: 'there are 2 elements "todo-item"' },
114
+ // en names the count first: run(target, count)
115
+ order: { en: [1, 0] },
116
+ run: (app, id, count) => app.expectCount(s(id), Number(count)),
117
+ },
118
+ // ---- Phase 6.2 – 6.4: more actions ----
119
+ {
120
+ name: 'doubleTap',
121
+ expressions: { zh: '双击 {string}', ja: '{string} をダブルタップする', en: 'I double tap {string}' },
122
+ examples: { zh: '双击 "photo"', ja: '"photo" をダブルタップする', en: 'I double tap "photo"' },
123
+ run: (app, id) => app.doubleTap(s(id)),
124
+ },
125
+ {
126
+ name: 'replaceText',
127
+ expressions: { zh: '把 {string} 的内容替换为 {string}', ja: '{string} の入力を {string} に置き換える', en: 'I replace the text of {string} with {string}' },
128
+ examples: { zh: '把 "todo-title" 的内容替换为 "买面包"', ja: '"todo-title" の入力を "パンを買う" に置き換える', en: 'I replace the text of "todo-title" with "Buy bread"' },
129
+ run: (app, id, text) => app.replaceText(s(id), s(text)),
130
+ },
131
+ {
132
+ name: 'pressEnter',
133
+ expressions: { zh: '在 {string} 中按回车', ja: '{string} でエンターキーを押す', en: 'I press enter in {string}' },
134
+ examples: { zh: '在 "todo-title" 中按回车', ja: '"todo-title" でエンターキーを押す', en: 'I press enter in "todo-title"' },
135
+ run: (app, id) => app.pressKey(s(id), 'enter'),
136
+ },
137
+ {
138
+ name: 'scroll',
139
+ expressions: { zh: '在 {string} 中向{word}滚动', ja: '{string} を{word}にスクロールする', en: 'I scroll {string} {word}' },
140
+ examples: { zh: '在 "todo-list" 中向下滚动', ja: '"todo-list" を下にスクロールする', en: 'I scroll "todo-list" down' },
141
+ run: (app, id, dir) => app.scroll(s(id), word('direction', DIRECTIONS, dir)),
142
+ },
143
+ {
144
+ name: 'scrollToEdge',
145
+ expressions: { zh: '把 {string} 滚动到{word}', ja: '{string} を{word}までスクロールする', en: 'I scroll {string} to the {word}' },
146
+ examples: { zh: '把 "todo-list" 滚动到底部', ja: '"todo-list" を一番下までスクロールする', en: 'I scroll "todo-list" to the bottom' },
147
+ run: (app, id, edge) => app.scrollToEdge(s(id), word('edge', EDGES, edge)),
148
+ },
149
+ {
150
+ name: 'swipe',
151
+ expressions: { zh: '在 {string} 上向{word}滑动', ja: '{string} を{word}にスワイプする', en: 'I swipe {word} on {string}' },
152
+ examples: { zh: '在 "todo-item" 上向左滑动', ja: '"todo-item" を左にスワイプする', en: 'I swipe left on "todo-item"' },
153
+ // en names the direction first: run(target, direction)
154
+ order: { en: [1, 0] },
155
+ run: (app, id, dir) => app.swipe(s(id), word('direction', DIRECTIONS, dir)),
156
+ },
157
+ {
158
+ name: 'drag',
159
+ expressions: { zh: '把 {string} 拖到 {string}', ja: '{string} を {string} までドラッグする', en: 'I drag {string} to {string}' },
160
+ examples: { zh: '把 "item-1" 拖到 "item-3"', ja: '"item-1" を "item-3" までドラッグする', en: 'I drag "item-1" to "item-3"' },
161
+ run: (app, from, to) => app.drag(s(from), s(to)),
162
+ },
163
+ {
164
+ name: 'pinch',
165
+ expressions: { zh: '在 {string} 上双指缩放到 {float} 倍', ja: '{string} を {float} 倍にピンチする', en: 'I pinch {string} to {float} times' },
166
+ examples: { zh: '在 "photo" 上双指缩放到 2 倍', ja: '"photo" を 2 倍にピンチする', en: 'I pinch "photo" to 2 times' },
167
+ run: (app, id, scale) => app.pinch(s(id), Number(scale)),
168
+ },
169
+ // ---- Phase 6.3: value controls ----
170
+ {
171
+ name: 'setSlider',
172
+ expressions: { zh: '把滑块 {string} 设为 {float}', ja: 'スライダー {string} を {float} にする', en: 'I set the slider {string} to {float}' },
173
+ examples: { zh: '把滑块 "volume" 设为 30', ja: 'スライダー "volume" を 30 にする', en: 'I set the slider "volume" to 30' },
174
+ run: (app, id, value) => app.setValue(s(id), Number(value)),
175
+ },
176
+ {
177
+ name: 'switchOn',
178
+ expressions: { zh: '打开开关 {string}', ja: 'スイッチ {string} をオンにする', en: 'I turn on the switch {string}' },
179
+ examples: { zh: '打开开关 "notify"', ja: 'スイッチ "notify" をオンにする', en: 'I turn on the switch "notify"' },
180
+ run: (app, id) => app.setValue(s(id), true),
181
+ },
182
+ {
183
+ name: 'switchOff',
184
+ expressions: { zh: '关闭开关 {string}', ja: 'スイッチ {string} をオフにする', en: 'I turn off the switch {string}' },
185
+ examples: { zh: '关闭开关 "notify"', ja: 'スイッチ "notify" をオフにする', en: 'I turn off the switch "notify"' },
186
+ run: (app, id) => app.setValue(s(id), false),
187
+ },
188
+ {
189
+ name: 'select',
190
+ expressions: { zh: '在 {string} 中选择 {string}', ja: '{string} で {string} を選ぶ', en: 'I select {string} in {string}' },
191
+ examples: { zh: '在 "priority" 中选择 "高"', ja: '"priority" で "高" を選ぶ', en: 'I select "High" in "priority"' },
192
+ // en names the option first: run(target, option)
193
+ order: { en: [1, 0] },
194
+ run: (app, id, option) => app.setValue(s(id), s(option)),
195
+ },
196
+ // ---- Phase 6.1: state assertions ----
197
+ {
198
+ name: 'enabled',
199
+ expressions: { zh: '元素 {string} 可用', ja: '要素 {string} が有効である', en: 'the element {string} is enabled' },
200
+ examples: { zh: '元素 "save" 可用', ja: '要素 "save" が有効である', en: 'the element "save" is enabled' },
201
+ run: (app, id) => app.expectEnabled(s(id)),
202
+ },
203
+ {
204
+ name: 'disabled',
205
+ expressions: { zh: '元素 {string} 不可用', ja: '要素 {string} が無効である', en: 'the element {string} is disabled' },
206
+ examples: { zh: '元素 "save" 不可用', ja: '要素 "save" が無効である', en: 'the element "save" is disabled' },
207
+ run: (app, id) => app.expectDisabled(s(id)),
208
+ },
209
+ {
210
+ name: 'checked',
211
+ expressions: { zh: '元素 {string} 已选中', ja: '要素 {string} がオンである', en: 'the element {string} is checked' },
212
+ examples: { zh: '元素 "notify" 已选中', ja: '要素 "notify" がオンである', en: 'the element "notify" is checked' },
213
+ run: (app, id) => app.expectChecked(s(id)),
214
+ },
215
+ {
216
+ name: 'notChecked',
217
+ expressions: { zh: '元素 {string} 未选中', ja: '要素 {string} がオフである', en: 'the element {string} is not checked' },
218
+ examples: { zh: '元素 "notify" 未选中', ja: '要素 "notify" がオフである', en: 'the element "notify" is not checked' },
219
+ run: (app, id) => app.expectNotChecked(s(id)),
220
+ },
221
+ {
222
+ name: 'focused',
223
+ expressions: { zh: '元素 {string} 获得焦点', ja: '要素 {string} にフォーカスがある', en: 'the element {string} has the focus' },
224
+ examples: { zh: '元素 "todo-title" 获得焦点', ja: '要素 "todo-title" にフォーカスがある', en: 'the element "todo-title" has the focus' },
225
+ run: (app, id) => app.expectFocused(s(id)),
226
+ },
227
+ {
228
+ name: 'hasValue',
229
+ expressions: { zh: '元素 {string} 的值是 {string}', ja: '要素 {string} の値が {string} である', en: 'the element {string} has the value {string}' },
230
+ examples: { zh: '元素 "priority" 的值是 "high"', ja: '要素 "priority" の値が "high" である', en: 'the element "priority" has the value "high"' },
231
+ run: (app, id, value) => app.expectValue(s(id), s(value)),
232
+ },
233
+ {
234
+ name: 'sliderValue',
235
+ expressions: { zh: '滑块 {string} 的值是 {float}', ja: 'スライダー {string} の値が {float} である', en: 'the slider {string} has the value {float}' },
236
+ examples: { zh: '滑块 "volume" 的值是 30', ja: 'スライダー "volume" の値が 30 である', en: 'the slider "volume" has the value 30' },
237
+ run: (app, id, value) => app.expectValue(s(id), Number(value)),
238
+ },
239
+ // ---- Phase 6.5: app lifecycle ----
240
+ {
241
+ name: 'background',
242
+ expressions: { zh: '把 App 切到后台', ja: 'アプリをバックグラウンドにする', en: 'I send the app to the background' },
243
+ examples: { zh: '把 App 切到后台', ja: 'アプリをバックグラウンドにする', en: 'I send the app to the background' },
244
+ run: (app) => app.device.sendToBackground(),
245
+ },
246
+ {
247
+ name: 'foreground',
248
+ expressions: { zh: '把 App 切回前台', ja: 'アプリをフォアグラウンドに戻す', en: 'I bring the app to the foreground' },
249
+ examples: { zh: '把 App 切回前台', ja: 'アプリをフォアグラウンドに戻す', en: 'I bring the app to the foreground' },
250
+ run: (app) => app.device.bringToForeground(),
251
+ },
252
+ {
253
+ name: 'relaunch',
254
+ expressions: { zh: '重新启动 App', ja: 'アプリを再起動する', en: 'I relaunch the app' },
255
+ examples: { zh: '重新启动 App', ja: 'アプリを再起動する', en: 'I relaunch the app' },
256
+ run: (app) => app.device.relaunch(),
257
+ },
258
+ // ---- Phase 6.6 / 6.7: device state and system UI ----
259
+ {
260
+ name: 'orientation',
261
+ expressions: { zh: '把设备转到{word}', ja: '画面を{word}にする', en: 'I rotate the device to {word}' },
262
+ examples: { zh: '把设备转到横屏', ja: '画面を横向きにする', en: 'I rotate the device to landscape' },
263
+ run: (app, o) => app.device.setOrientation(word('orientation', ORIENTATIONS, o)),
264
+ },
265
+ {
266
+ name: 'appearance',
267
+ expressions: { zh: '把外观设为{word}模式', ja: '外観を{word}モードにする', en: 'I switch to {word} mode' },
268
+ examples: { zh: '把外观设为深色模式', ja: '外観をダークモードにする', en: 'I switch to dark mode' },
269
+ run: (app, a) => app.device.setAppearance(word('appearance', APPEARANCES, a)),
270
+ },
271
+ {
272
+ name: 'location',
273
+ expressions: { zh: '把设备位置设为 {float}, {float}', ja: '端末の位置を {float}, {float} にする', en: 'I set the device location to {float}, {float}' },
274
+ examples: { zh: '把设备位置设为 35.68, 139.76', ja: '端末の位置を 35.68, 139.76 にする', en: 'I set the device location to 35.68, 139.76' },
275
+ run: (app, lat, lon) => app.device.setLocation(Number(lat), Number(lon)),
276
+ },
277
+ {
278
+ name: 'offline',
279
+ expressions: { zh: '把设备切换到离线', ja: '端末をオフラインにする', en: 'I go offline' },
280
+ examples: { zh: '把设备切换到离线', ja: '端末をオフラインにする', en: 'I go offline' },
281
+ run: (app) => app.device.setNetwork('offline'),
282
+ },
283
+ {
284
+ name: 'online',
285
+ expressions: { zh: '把设备切换到在线', ja: '端末をオンラインにする', en: 'I go online' },
286
+ examples: { zh: '把设备切换到在线', ja: '端末をオンラインにする', en: 'I go online' },
287
+ run: (app) => app.device.setNetwork('online'),
288
+ },
289
+ {
290
+ name: 'grantPermission',
291
+ expressions: { zh: '授予 App {word}权限', ja: 'アプリに{word}の権限を与える', en: 'I grant the {word} permission' },
292
+ examples: { zh: '授予 App 定位权限', ja: 'アプリに位置情報の権限を与える', en: 'I grant the location permission' },
293
+ run: (app, p) => app.device.setPermission(word('permission', PERMISSIONS, p), 'grant'),
294
+ },
295
+ {
296
+ name: 'revokePermission',
297
+ expressions: { zh: '撤销 App 的{word}权限', ja: 'アプリの{word}の権限を取り消す', en: 'I revoke the {word} permission' },
298
+ examples: { zh: '撤销 App 的定位权限', ja: 'アプリの位置情報の権限を取り消す', en: 'I revoke the location permission' },
299
+ run: (app, p) => app.device.setPermission(word('permission', PERMISSIONS, p), 'revoke'),
300
+ },
301
+ {
302
+ name: 'allowDialog',
303
+ expressions: { zh: '在系统弹窗中允许', ja: 'システムダイアログで許可する', en: 'I allow in the system dialog' },
304
+ examples: { zh: '在系统弹窗中允许', ja: 'システムダイアログで許可する', en: 'I allow in the system dialog' },
305
+ run: (app) => app.device.system.allowPermission(),
306
+ },
307
+ {
308
+ name: 'denyDialog',
309
+ expressions: { zh: '在系统弹窗中拒绝', ja: 'システムダイアログで拒否する', en: 'I deny in the system dialog' },
310
+ examples: { zh: '在系统弹窗中拒绝', ja: 'システムダイアログで拒否する', en: 'I deny in the system dialog' },
311
+ run: (app) => app.device.system.denyPermission(),
312
+ },
313
+ {
314
+ name: 'shake',
315
+ expressions: { zh: '摇动设备', ja: '端末を振る', en: 'I shake the device' },
316
+ examples: { zh: '摇动设备', ja: '端末を振る', en: 'I shake the device' },
317
+ run: (app) => app.device.shake(),
318
+ },
319
+ // ---- Phase 6.9: evidence ----
320
+ {
321
+ name: 'screenshot',
322
+ expressions: { zh: '截图并保存为 {string}', ja: 'スクリーンショットを {string} として保存する', en: 'I take a screenshot named {string}' },
323
+ examples: { zh: '截图并保存为 "列表"', ja: 'スクリーンショットを "一覧" として保存する', en: 'I take a screenshot named "list"' },
324
+ run: (app, name) => app.device.screenshot(undefined, { name: s(name) }),
325
+ },
326
+ ];
327
+ /** Arguments as `run` expects them, from the parameters in the order the language's expression has them. */
328
+ export function argsFor(step, language, params) {
329
+ const order = step.order?.[language];
330
+ return order === undefined ? [...params] : order.map((i) => params[i]);
331
+ }
332
+ //# sourceMappingURL=steps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"steps.js","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "@quiesco/steps",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Optional generic cucumber steps for Quiesco (prototyping only; write business-language steps for real projects)",
5
+ "license": "MIT",
6
+ "author": "h2nology",
7
+ "keywords": [
8
+ "quiesco",
9
+ "e2e",
10
+ "testing",
11
+ "expo",
12
+ "react-native",
13
+ "cucumber",
14
+ "gherkin",
15
+ "steps"
16
+ ],
17
+ "homepage": "https://github.com/h2nology/quiesco/tree/main/packages/steps#readme",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/h2nology/quiesco.git",
21
+ "directory": "packages/steps"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/h2nology/quiesco/issues"
25
+ },
26
+ "engines": {
27
+ "node": ">=24"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "type": "module",
33
+ "main": "./dist/index.js",
34
+ "types": "./dist/index.d.ts",
35
+ "exports": {
36
+ ".": {
37
+ "types": "./dist/index.d.ts",
38
+ "import": "./dist/index.js"
39
+ }
40
+ },
41
+ "files": [
42
+ "dist",
43
+ "src"
44
+ ],
45
+ "dependencies": {
46
+ "@quiesco/core": "0.1.0",
47
+ "@quiesco/cucumber": "0.1.0"
48
+ },
49
+ "devDependencies": {
50
+ "@cucumber/cucumber": "13.3.0",
51
+ "@cucumber/cucumber-expressions": "20.1.0"
52
+ },
53
+ "peerDependencies": {
54
+ "@cucumber/cucumber": ">=12"
55
+ },
56
+ "scripts": {
57
+ "build": "tsc -b",
58
+ "test": "vitest run"
59
+ }
6
60
  }
package/src/index.ts ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @quiesco/steps — optional generic steps (requirements.md §6.11).
3
+ *
4
+ * // features/support/steps.ts
5
+ * import { registerQuiescoSteps } from '@quiesco/steps';
6
+ * registerQuiescoSteps({ languages: ['zh'] });
7
+ *
8
+ * Only for prototyping and exploration: these steps script taps and texts instead of stating what the
9
+ * user wants. Real projects write their own business-language steps (see docs/driver-pattern.mdx).
10
+ */
11
+ import { defineStep } from '@cucumber/cucumber';
12
+ import type { QuiescoWorld } from '@quiesco/cucumber';
13
+
14
+ import type { GenericStep, Language } from './steps.js';
15
+ import { GENERIC_STEPS, argsFor } from './steps.js';
16
+
17
+ export { GENERIC_STEPS, argsFor } from './steps.js';
18
+ export type { GenericStep, Language } from './steps.js';
19
+
20
+ export interface RegisterStepsOptions {
21
+ /** Step languages to register. Default: all (zh, ja, en). */
22
+ languages?: readonly Language[];
23
+ /** Only these steps (by `name`). Default: all. */
24
+ only?: readonly string[];
25
+ }
26
+
27
+ type Define = (expression: string, fn: (this: QuiescoWorld, ...params: (string | number)[]) => Promise<void>) => void;
28
+
29
+ /** Number of `{…}` parameters in a cucumber expression. */
30
+ export function parameterCount(expression: string): number {
31
+ return (expression.match(/\{[^}]*\}/g) ?? []).length;
32
+ }
33
+
34
+ type Body = (world: QuiescoWorld, params: (string | number)[]) => Promise<void>;
35
+
36
+ /**
37
+ * cucumber-js checks a step function's arity against the expression's parameters, so the wrapper must
38
+ * declare exactly that many (a rest parameter has length 0).
39
+ */
40
+ function withArity(count: number, body: Body): (this: QuiescoWorld, ...params: (string | number)[]) => Promise<void> {
41
+ switch (count) {
42
+ case 0:
43
+ return async function (this: QuiescoWorld) {
44
+ await body(this, []);
45
+ };
46
+ case 1:
47
+ return async function (this: QuiescoWorld, a: string | number) {
48
+ await body(this, [a]);
49
+ };
50
+ case 2:
51
+ return async function (this: QuiescoWorld, a: string | number, b: string | number) {
52
+ await body(this, [a, b]);
53
+ };
54
+ default:
55
+ throw new Error(`generic steps take at most 2 parameters (got ${count})`);
56
+ }
57
+ }
58
+
59
+ /** Register the generic steps with cucumber-js. Returns the expressions it registered. */
60
+ export function registerQuiescoSteps(options: RegisterStepsOptions = {}, define: Define = defineStep as unknown as Define): string[] {
61
+ const languages = options.languages ?? (['zh', 'ja', 'en'] as const);
62
+ const steps: readonly GenericStep[] = options.only === undefined ? GENERIC_STEPS : GENERIC_STEPS.filter((st) => options.only?.includes(st.name));
63
+ const registered: string[] = [];
64
+ for (const step of steps) {
65
+ for (const language of languages) {
66
+ const expression = step.expressions[language];
67
+ define(expression, withArity(parameterCount(expression), async (world, params) => {
68
+ await step.run(world.app, ...argsFor(step, language, params));
69
+ }));
70
+ registered.push(expression);
71
+ }
72
+ }
73
+ return registered;
74
+ }
package/src/steps.ts ADDED
@@ -0,0 +1,362 @@
1
+ /**
2
+ * The generic steps (requirements.md §6.11), in Chinese, Japanese and English.
3
+ *
4
+ * These are operation scripts, not requirements: they describe taps and texts, not what the user wants
5
+ * to achieve. Use them while prototyping or exploring; a real project writes business-language steps
6
+ * ("When the user logs in as …") on top of `this.app` or its own driver.
7
+ *
8
+ * A quoted target is a testID (`点击 "add-todo"`); the "…文本 / テキスト / the text …" variants match
9
+ * the displayed text instead.
10
+ */
11
+ import type { Driver } from '@quiesco/core';
12
+
13
+ export type Language = 'zh' | 'ja' | 'en';
14
+
15
+ export interface GenericStep {
16
+ /** Stable name, e.g. for docs. */
17
+ name: string;
18
+ /** Cucumber expression per language. Parameters are passed to `run` in the order they appear. */
19
+ expressions: Record<Language, string>;
20
+ /** Example step text per language (docs and tests). */
21
+ examples: Record<Language, string>;
22
+ /** Maps the language's parameters to `run` arguments when their order differs from `zh`. */
23
+ order?: Partial<Record<Language, number[]>>;
24
+ run(app: Driver, ...args: (string | number)[]): Promise<unknown>;
25
+ }
26
+
27
+ const s = (v: string | number | undefined): string => String(v);
28
+
29
+ /** A word from the step text mapped to an API value; the same table serves every language. */
30
+ function word<T>(kind: string, table: Record<string, T>, v: string | number | undefined): T {
31
+ const key = s(v).trim().toLowerCase();
32
+ const hit = table[key];
33
+ if (hit === undefined) throw new Error(`Unknown ${kind} "${s(v)}": use one of ${Object.keys(table).join(', ')}`);
34
+ return hit;
35
+ }
36
+
37
+ type Direction = 'up' | 'down' | 'left' | 'right';
38
+ const DIRECTIONS: Record<string, Direction> = { 上: 'up', 下: 'down', 左: 'left', 右: 'right', up: 'up', down: 'down', left: 'left', right: 'right' };
39
+ type Edge = 'top' | 'bottom' | 'left' | 'right';
40
+ const EDGES: Record<string, Edge> = {
41
+ 顶部: 'top', 底部: 'bottom', 最左边: 'left', 最右边: 'right',
42
+ 一番上: 'top', 一番下: 'bottom', 左端: 'left', 右端: 'right',
43
+ top: 'top', bottom: 'bottom', left: 'left', right: 'right',
44
+ };
45
+ const ORIENTATIONS: Record<string, 'portrait' | 'landscape'> = { 竖屏: 'portrait', 横屏: 'landscape', 縦向き: 'portrait', 横向き: 'landscape', portrait: 'portrait', landscape: 'landscape' };
46
+ const APPEARANCES: Record<string, 'light' | 'dark'> = { 浅色: 'light', 深色: 'dark', ライト: 'light', ダーク: 'dark', light: 'light', dark: 'dark' };
47
+ type Permission = 'camera' | 'microphone' | 'location' | 'notifications' | 'contacts' | 'photos' | 'calendar';
48
+ const PERMISSIONS: Record<string, Permission> = {
49
+ 相机: 'camera', 麦克风: 'microphone', 定位: 'location', 通知: 'notifications', 通讯录: 'contacts', 照片: 'photos', 日历: 'calendar',
50
+ カメラ: 'camera', マイク: 'microphone', 位置情報: 'location', 連絡先: 'contacts', 写真: 'photos', カレンダー: 'calendar',
51
+ camera: 'camera', microphone: 'microphone', location: 'location', notifications: 'notifications', contacts: 'contacts', photos: 'photos', calendar: 'calendar',
52
+ };
53
+
54
+ export const GENERIC_STEPS: readonly GenericStep[] = [
55
+ {
56
+ name: 'tap',
57
+ expressions: { zh: '点击 {string}', ja: '{string} をタップする', en: 'I tap {string}' },
58
+ examples: { zh: '点击 "add-todo"', ja: '"add-todo" をタップする', en: 'I tap "add-todo"' },
59
+ run: (app, id) => app.tap(s(id)),
60
+ },
61
+ {
62
+ name: 'tapText',
63
+ expressions: { zh: '点击文本 {string}', ja: 'テキスト {string} をタップする', en: 'I tap the text {string}' },
64
+ examples: { zh: '点击文本 "保存"', ja: 'テキスト "保存" をタップする', en: 'I tap the text "Save"' },
65
+ run: (app, text) => app.tap({ text: s(text) }),
66
+ },
67
+ {
68
+ name: 'longPress',
69
+ expressions: { zh: '长按 {string}', ja: '{string} を長押しする', en: 'I long press {string}' },
70
+ examples: { zh: '长按 "todo-item"', ja: '"todo-item" を長押しする', en: 'I long press "todo-item"' },
71
+ run: (app, id) => app.longPress(s(id)),
72
+ },
73
+ {
74
+ name: 'type',
75
+ expressions: { zh: '在 {string} 中输入 {string}', ja: '{string} に {string} と入力する', en: 'I type {string} into {string}' },
76
+ examples: { zh: '在 "todo-title" 中输入 "买牛奶"', ja: '"todo-title" に "牛乳を買う" と入力する', en: 'I type "Buy milk" into "todo-title"' },
77
+ // en names the text first: run(target, text)
78
+ order: { en: [1, 0] },
79
+ run: (app, id, text) => app.type(s(id), s(text)),
80
+ },
81
+ {
82
+ name: 'clear',
83
+ expressions: { zh: '清空 {string} 的内容', ja: '{string} の入力をクリアする', en: 'I clear {string}' },
84
+ examples: { zh: '清空 "todo-title" 的内容', ja: '"todo-title" の入力をクリアする', en: 'I clear "todo-title"' },
85
+ run: (app, id) => app.clear(s(id)),
86
+ },
87
+ {
88
+ name: 'scrollTo',
89
+ expressions: { zh: '在 {string} 中滚动到 {string}', ja: '{string} を {string} までスクロールする', en: 'I scroll {string} to {string}' },
90
+ examples: { zh: '在 "todo-list" 中滚动到 "todo-item-30"', ja: '"todo-list" を "todo-item-30" までスクロールする', en: 'I scroll "todo-list" to "todo-item-30"' },
91
+ run: (app, container, target) => app.scrollTo(s(container), s(target)),
92
+ },
93
+ {
94
+ name: 'dismissKeyboard',
95
+ expressions: { zh: '收起键盘', ja: 'キーボードを閉じる', en: 'I dismiss the keyboard' },
96
+ examples: { zh: '收起键盘', ja: 'キーボードを閉じる', en: 'I dismiss the keyboard' },
97
+ run: (app) => app.dismissKeyboard(),
98
+ },
99
+ {
100
+ name: 'pressBack',
101
+ expressions: { zh: '按返回键', ja: '戻るボタンを押す', en: 'I press back' },
102
+ examples: { zh: '按返回键', ja: '戻るボタンを押す', en: 'I press back' },
103
+ run: (app) => app.pressBack(),
104
+ },
105
+ {
106
+ name: 'openUrl',
107
+ expressions: { zh: '打开链接 {string}', ja: 'リンク {string} を開く', en: 'I open the link {string}' },
108
+ examples: { zh: '打开链接 "myapp://todos"', ja: 'リンク "myapp://todos" を開く', en: 'I open the link "myapp://todos"' },
109
+ run: (app, url) => app.openUrl(s(url)),
110
+ },
111
+ {
112
+ name: 'seeText',
113
+ expressions: { zh: '看到文本 {string}', ja: 'テキスト {string} が表示されている', en: 'I see the text {string}' },
114
+ examples: { zh: '看到文本 "买牛奶"', ja: 'テキスト "牛乳を買う" が表示されている', en: 'I see the text "Buy milk"' },
115
+ run: (app, text) => app.expectVisible({ text: s(text) }),
116
+ },
117
+ {
118
+ name: 'notSeeText',
119
+ expressions: { zh: '看不到文本 {string}', ja: 'テキスト {string} が表示されていない', en: 'I do not see the text {string}' },
120
+ examples: { zh: '看不到文本 "买牛奶"', ja: 'テキスト "牛乳を買う" が表示されていない', en: 'I do not see the text "Buy milk"' },
121
+ run: (app, text) => app.expectNotVisible({ text: s(text) }),
122
+ },
123
+ {
124
+ name: 'visible',
125
+ expressions: { zh: '元素 {string} 可见', ja: '要素 {string} が表示されている', en: 'the element {string} is visible' },
126
+ examples: { zh: '元素 "todo-list" 可见', ja: '要素 "todo-list" が表示されている', en: 'the element "todo-list" is visible' },
127
+ run: (app, id) => app.expectVisible(s(id)),
128
+ },
129
+ {
130
+ name: 'notVisible',
131
+ expressions: { zh: '元素 {string} 不可见', ja: '要素 {string} が表示されていない', en: 'the element {string} is not visible' },
132
+ examples: { zh: '元素 "todo-title-error" 不可见', ja: '要素 "todo-title-error" が表示されていない', en: 'the element "todo-title-error" is not visible' },
133
+ run: (app, id) => app.expectNotVisible(s(id)),
134
+ },
135
+ {
136
+ name: 'hasText',
137
+ expressions: { zh: '元素 {string} 的文本是 {string}', ja: '要素 {string} のテキストが {string} である', en: 'the element {string} has the text {string}' },
138
+ examples: { zh: '元素 "greeting" 的文本是 "a@example.com"', ja: '要素 "greeting" のテキストが "a@example.com" である', en: 'the element "greeting" has the text "a@example.com"' },
139
+ run: (app, id, text) => app.expectText(s(id), s(text)),
140
+ },
141
+ {
142
+ name: 'count',
143
+ expressions: { zh: '元素 {string} 共有 {int} 个', ja: '要素 {string} が {int} 個ある', en: 'there are {int} elements {string}' },
144
+ examples: { zh: '元素 "todo-item" 共有 2 个', ja: '要素 "todo-item" が 2 個ある', en: 'there are 2 elements "todo-item"' },
145
+ // en names the count first: run(target, count)
146
+ order: { en: [1, 0] },
147
+ run: (app, id, count) => app.expectCount(s(id), Number(count)),
148
+ },
149
+ // ---- Phase 6.2 – 6.4: more actions ----
150
+ {
151
+ name: 'doubleTap',
152
+ expressions: { zh: '双击 {string}', ja: '{string} をダブルタップする', en: 'I double tap {string}' },
153
+ examples: { zh: '双击 "photo"', ja: '"photo" をダブルタップする', en: 'I double tap "photo"' },
154
+ run: (app, id) => app.doubleTap(s(id)),
155
+ },
156
+ {
157
+ name: 'replaceText',
158
+ expressions: { zh: '把 {string} 的内容替换为 {string}', ja: '{string} の入力を {string} に置き換える', en: 'I replace the text of {string} with {string}' },
159
+ examples: { zh: '把 "todo-title" 的内容替换为 "买面包"', ja: '"todo-title" の入力を "パンを買う" に置き換える', en: 'I replace the text of "todo-title" with "Buy bread"' },
160
+ run: (app, id, text) => app.replaceText(s(id), s(text)),
161
+ },
162
+ {
163
+ name: 'pressEnter',
164
+ expressions: { zh: '在 {string} 中按回车', ja: '{string} でエンターキーを押す', en: 'I press enter in {string}' },
165
+ examples: { zh: '在 "todo-title" 中按回车', ja: '"todo-title" でエンターキーを押す', en: 'I press enter in "todo-title"' },
166
+ run: (app, id) => app.pressKey(s(id), 'enter'),
167
+ },
168
+ {
169
+ name: 'scroll',
170
+ expressions: { zh: '在 {string} 中向{word}滚动', ja: '{string} を{word}にスクロールする', en: 'I scroll {string} {word}' },
171
+ examples: { zh: '在 "todo-list" 中向下滚动', ja: '"todo-list" を下にスクロールする', en: 'I scroll "todo-list" down' },
172
+ run: (app, id, dir) => app.scroll(s(id), word('direction', DIRECTIONS, dir)),
173
+ },
174
+ {
175
+ name: 'scrollToEdge',
176
+ expressions: { zh: '把 {string} 滚动到{word}', ja: '{string} を{word}までスクロールする', en: 'I scroll {string} to the {word}' },
177
+ examples: { zh: '把 "todo-list" 滚动到底部', ja: '"todo-list" を一番下までスクロールする', en: 'I scroll "todo-list" to the bottom' },
178
+ run: (app, id, edge) => app.scrollToEdge(s(id), word('edge', EDGES, edge)),
179
+ },
180
+ {
181
+ name: 'swipe',
182
+ expressions: { zh: '在 {string} 上向{word}滑动', ja: '{string} を{word}にスワイプする', en: 'I swipe {word} on {string}' },
183
+ examples: { zh: '在 "todo-item" 上向左滑动', ja: '"todo-item" を左にスワイプする', en: 'I swipe left on "todo-item"' },
184
+ // en names the direction first: run(target, direction)
185
+ order: { en: [1, 0] },
186
+ run: (app, id, dir) => app.swipe(s(id), word('direction', DIRECTIONS, dir)),
187
+ },
188
+ {
189
+ name: 'drag',
190
+ expressions: { zh: '把 {string} 拖到 {string}', ja: '{string} を {string} までドラッグする', en: 'I drag {string} to {string}' },
191
+ examples: { zh: '把 "item-1" 拖到 "item-3"', ja: '"item-1" を "item-3" までドラッグする', en: 'I drag "item-1" to "item-3"' },
192
+ run: (app, from, to) => app.drag(s(from), s(to)),
193
+ },
194
+ {
195
+ name: 'pinch',
196
+ expressions: { zh: '在 {string} 上双指缩放到 {float} 倍', ja: '{string} を {float} 倍にピンチする', en: 'I pinch {string} to {float} times' },
197
+ examples: { zh: '在 "photo" 上双指缩放到 2 倍', ja: '"photo" を 2 倍にピンチする', en: 'I pinch "photo" to 2 times' },
198
+ run: (app, id, scale) => app.pinch(s(id), Number(scale)),
199
+ },
200
+ // ---- Phase 6.3: value controls ----
201
+ {
202
+ name: 'setSlider',
203
+ expressions: { zh: '把滑块 {string} 设为 {float}', ja: 'スライダー {string} を {float} にする', en: 'I set the slider {string} to {float}' },
204
+ examples: { zh: '把滑块 "volume" 设为 30', ja: 'スライダー "volume" を 30 にする', en: 'I set the slider "volume" to 30' },
205
+ run: (app, id, value) => app.setValue(s(id), Number(value)),
206
+ },
207
+ {
208
+ name: 'switchOn',
209
+ expressions: { zh: '打开开关 {string}', ja: 'スイッチ {string} をオンにする', en: 'I turn on the switch {string}' },
210
+ examples: { zh: '打开开关 "notify"', ja: 'スイッチ "notify" をオンにする', en: 'I turn on the switch "notify"' },
211
+ run: (app, id) => app.setValue(s(id), true),
212
+ },
213
+ {
214
+ name: 'switchOff',
215
+ expressions: { zh: '关闭开关 {string}', ja: 'スイッチ {string} をオフにする', en: 'I turn off the switch {string}' },
216
+ examples: { zh: '关闭开关 "notify"', ja: 'スイッチ "notify" をオフにする', en: 'I turn off the switch "notify"' },
217
+ run: (app, id) => app.setValue(s(id), false),
218
+ },
219
+ {
220
+ name: 'select',
221
+ expressions: { zh: '在 {string} 中选择 {string}', ja: '{string} で {string} を選ぶ', en: 'I select {string} in {string}' },
222
+ examples: { zh: '在 "priority" 中选择 "高"', ja: '"priority" で "高" を選ぶ', en: 'I select "High" in "priority"' },
223
+ // en names the option first: run(target, option)
224
+ order: { en: [1, 0] },
225
+ run: (app, id, option) => app.setValue(s(id), s(option)),
226
+ },
227
+ // ---- Phase 6.1: state assertions ----
228
+ {
229
+ name: 'enabled',
230
+ expressions: { zh: '元素 {string} 可用', ja: '要素 {string} が有効である', en: 'the element {string} is enabled' },
231
+ examples: { zh: '元素 "save" 可用', ja: '要素 "save" が有効である', en: 'the element "save" is enabled' },
232
+ run: (app, id) => app.expectEnabled(s(id)),
233
+ },
234
+ {
235
+ name: 'disabled',
236
+ expressions: { zh: '元素 {string} 不可用', ja: '要素 {string} が無効である', en: 'the element {string} is disabled' },
237
+ examples: { zh: '元素 "save" 不可用', ja: '要素 "save" が無効である', en: 'the element "save" is disabled' },
238
+ run: (app, id) => app.expectDisabled(s(id)),
239
+ },
240
+ {
241
+ name: 'checked',
242
+ expressions: { zh: '元素 {string} 已选中', ja: '要素 {string} がオンである', en: 'the element {string} is checked' },
243
+ examples: { zh: '元素 "notify" 已选中', ja: '要素 "notify" がオンである', en: 'the element "notify" is checked' },
244
+ run: (app, id) => app.expectChecked(s(id)),
245
+ },
246
+ {
247
+ name: 'notChecked',
248
+ expressions: { zh: '元素 {string} 未选中', ja: '要素 {string} がオフである', en: 'the element {string} is not checked' },
249
+ examples: { zh: '元素 "notify" 未选中', ja: '要素 "notify" がオフである', en: 'the element "notify" is not checked' },
250
+ run: (app, id) => app.expectNotChecked(s(id)),
251
+ },
252
+ {
253
+ name: 'focused',
254
+ expressions: { zh: '元素 {string} 获得焦点', ja: '要素 {string} にフォーカスがある', en: 'the element {string} has the focus' },
255
+ examples: { zh: '元素 "todo-title" 获得焦点', ja: '要素 "todo-title" にフォーカスがある', en: 'the element "todo-title" has the focus' },
256
+ run: (app, id) => app.expectFocused(s(id)),
257
+ },
258
+ {
259
+ name: 'hasValue',
260
+ expressions: { zh: '元素 {string} 的值是 {string}', ja: '要素 {string} の値が {string} である', en: 'the element {string} has the value {string}' },
261
+ examples: { zh: '元素 "priority" 的值是 "high"', ja: '要素 "priority" の値が "high" である', en: 'the element "priority" has the value "high"' },
262
+ run: (app, id, value) => app.expectValue(s(id), s(value)),
263
+ },
264
+ {
265
+ name: 'sliderValue',
266
+ expressions: { zh: '滑块 {string} 的值是 {float}', ja: 'スライダー {string} の値が {float} である', en: 'the slider {string} has the value {float}' },
267
+ examples: { zh: '滑块 "volume" 的值是 30', ja: 'スライダー "volume" の値が 30 である', en: 'the slider "volume" has the value 30' },
268
+ run: (app, id, value) => app.expectValue(s(id), Number(value)),
269
+ },
270
+ // ---- Phase 6.5: app lifecycle ----
271
+ {
272
+ name: 'background',
273
+ expressions: { zh: '把 App 切到后台', ja: 'アプリをバックグラウンドにする', en: 'I send the app to the background' },
274
+ examples: { zh: '把 App 切到后台', ja: 'アプリをバックグラウンドにする', en: 'I send the app to the background' },
275
+ run: (app) => app.device.sendToBackground(),
276
+ },
277
+ {
278
+ name: 'foreground',
279
+ expressions: { zh: '把 App 切回前台', ja: 'アプリをフォアグラウンドに戻す', en: 'I bring the app to the foreground' },
280
+ examples: { zh: '把 App 切回前台', ja: 'アプリをフォアグラウンドに戻す', en: 'I bring the app to the foreground' },
281
+ run: (app) => app.device.bringToForeground(),
282
+ },
283
+ {
284
+ name: 'relaunch',
285
+ expressions: { zh: '重新启动 App', ja: 'アプリを再起動する', en: 'I relaunch the app' },
286
+ examples: { zh: '重新启动 App', ja: 'アプリを再起動する', en: 'I relaunch the app' },
287
+ run: (app) => app.device.relaunch(),
288
+ },
289
+ // ---- Phase 6.6 / 6.7: device state and system UI ----
290
+ {
291
+ name: 'orientation',
292
+ expressions: { zh: '把设备转到{word}', ja: '画面を{word}にする', en: 'I rotate the device to {word}' },
293
+ examples: { zh: '把设备转到横屏', ja: '画面を横向きにする', en: 'I rotate the device to landscape' },
294
+ run: (app, o) => app.device.setOrientation(word('orientation', ORIENTATIONS, o)),
295
+ },
296
+ {
297
+ name: 'appearance',
298
+ expressions: { zh: '把外观设为{word}模式', ja: '外観を{word}モードにする', en: 'I switch to {word} mode' },
299
+ examples: { zh: '把外观设为深色模式', ja: '外観をダークモードにする', en: 'I switch to dark mode' },
300
+ run: (app, a) => app.device.setAppearance(word('appearance', APPEARANCES, a)),
301
+ },
302
+ {
303
+ name: 'location',
304
+ expressions: { zh: '把设备位置设为 {float}, {float}', ja: '端末の位置を {float}, {float} にする', en: 'I set the device location to {float}, {float}' },
305
+ examples: { zh: '把设备位置设为 35.68, 139.76', ja: '端末の位置を 35.68, 139.76 にする', en: 'I set the device location to 35.68, 139.76' },
306
+ run: (app, lat, lon) => app.device.setLocation(Number(lat), Number(lon)),
307
+ },
308
+ {
309
+ name: 'offline',
310
+ expressions: { zh: '把设备切换到离线', ja: '端末をオフラインにする', en: 'I go offline' },
311
+ examples: { zh: '把设备切换到离线', ja: '端末をオフラインにする', en: 'I go offline' },
312
+ run: (app) => app.device.setNetwork('offline'),
313
+ },
314
+ {
315
+ name: 'online',
316
+ expressions: { zh: '把设备切换到在线', ja: '端末をオンラインにする', en: 'I go online' },
317
+ examples: { zh: '把设备切换到在线', ja: '端末をオンラインにする', en: 'I go online' },
318
+ run: (app) => app.device.setNetwork('online'),
319
+ },
320
+ {
321
+ name: 'grantPermission',
322
+ expressions: { zh: '授予 App {word}权限', ja: 'アプリに{word}の権限を与える', en: 'I grant the {word} permission' },
323
+ examples: { zh: '授予 App 定位权限', ja: 'アプリに位置情報の権限を与える', en: 'I grant the location permission' },
324
+ run: (app, p) => app.device.setPermission(word('permission', PERMISSIONS, p), 'grant'),
325
+ },
326
+ {
327
+ name: 'revokePermission',
328
+ expressions: { zh: '撤销 App 的{word}权限', ja: 'アプリの{word}の権限を取り消す', en: 'I revoke the {word} permission' },
329
+ examples: { zh: '撤销 App 的定位权限', ja: 'アプリの位置情報の権限を取り消す', en: 'I revoke the location permission' },
330
+ run: (app, p) => app.device.setPermission(word('permission', PERMISSIONS, p), 'revoke'),
331
+ },
332
+ {
333
+ name: 'allowDialog',
334
+ expressions: { zh: '在系统弹窗中允许', ja: 'システムダイアログで許可する', en: 'I allow in the system dialog' },
335
+ examples: { zh: '在系统弹窗中允许', ja: 'システムダイアログで許可する', en: 'I allow in the system dialog' },
336
+ run: (app) => app.device.system.allowPermission(),
337
+ },
338
+ {
339
+ name: 'denyDialog',
340
+ expressions: { zh: '在系统弹窗中拒绝', ja: 'システムダイアログで拒否する', en: 'I deny in the system dialog' },
341
+ examples: { zh: '在系统弹窗中拒绝', ja: 'システムダイアログで拒否する', en: 'I deny in the system dialog' },
342
+ run: (app) => app.device.system.denyPermission(),
343
+ },
344
+ {
345
+ name: 'shake',
346
+ expressions: { zh: '摇动设备', ja: '端末を振る', en: 'I shake the device' },
347
+ examples: { zh: '摇动设备', ja: '端末を振る', en: 'I shake the device' },
348
+ run: (app) => app.device.shake(),
349
+ },
350
+ // ---- Phase 6.9: evidence ----
351
+ {
352
+ name: 'screenshot',
353
+ expressions: { zh: '截图并保存为 {string}', ja: 'スクリーンショットを {string} として保存する', en: 'I take a screenshot named {string}' },
354
+ examples: { zh: '截图并保存为 "列表"', ja: 'スクリーンショットを "一覧" として保存する', en: 'I take a screenshot named "list"' },
355
+ run: (app, name) => app.device.screenshot(undefined, { name: s(name) }),
356
+ },
357
+ ];
358
+ /** Arguments as `run` expects them, from the parameters in the order the language's expression has them. */
359
+ export function argsFor(step: GenericStep, language: Language, params: readonly (string | number)[]): (string | number)[] {
360
+ const order = step.order?.[language];
361
+ return order === undefined ? [...params] : order.map((i) => params[i] as string | number);
362
+ }