@sun-asterisk/sungen 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +3 -2
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
- package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/assertion-patterns.js +12 -13
- package/dist/generators/test-generator/patterns/assertion-patterns.js.map +1 -1
- package/dist/generators/test-generator/patterns/interaction-patterns.js +4 -4
- package/dist/generators/test-generator/patterns/interaction-patterns.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +14 -14
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +3 -6
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +62 -58
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +0 -1
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +42 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md +60 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +59 -0
- package/dist/orchestrator/templates/ai-instructions/claude-config.md +90 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +27 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +123 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +94 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +41 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-tc.md +59 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +58 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-config.md +90 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-skill-error-mapping.md +27 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-skill-gherkin-syntax.md +123 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-skill-selector-keys.md +94 -0
- package/dist/orchestrator/templates/readme.md +43 -39
- package/docs/gherkin standards/gherkin-core-standard.md +141 -90
- package/docs/gherkin standards/gherkin-core-standard.vi.md +264 -54
- package/package.json +2 -2
- package/src/cli/commands/init.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
- package/src/generators/test-generator/patterns/assertion-patterns.ts +12 -13
- package/src/generators/test-generator/patterns/interaction-patterns.ts +4 -4
- package/src/generators/test-generator/utils/selector-resolver.ts +15 -15
- package/src/orchestrator/project-initializer.ts +74 -58
- package/src/orchestrator/screen-manager.ts +0 -1
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +42 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md +60 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +59 -0
- package/src/orchestrator/templates/ai-instructions/claude-config.md +90 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +27 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +123 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +94 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +41 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-tc.md +59 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +58 -0
- package/src/orchestrator/templates/ai-instructions/copilot-config.md +90 -0
- package/src/orchestrator/templates/ai-instructions/copilot-skill-error-mapping.md +27 -0
- package/src/orchestrator/templates/ai-instructions/copilot-skill-gherkin-syntax.md +123 -0
- package/src/orchestrator/templates/ai-instructions/copilot-skill-selector-keys.md +94 -0
- package/src/orchestrator/templates/readme.md +43 -39
- package/dist/orchestrator/templates/ai-rules.md +0 -189
- package/src/orchestrator/templates/ai-rules.md +0 -189
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Tiêu Chuẩn Gherkin - Cốt Lõi
|
|
1
|
+
# Tiêu Chuẩn Gherkin - Cốt Lõi v2
|
|
2
2
|
|
|
3
3
|
**Áp dụng cho: Kiểm thử thủ công & Kiểm thử tự động**
|
|
4
4
|
|
|
@@ -23,7 +23,7 @@ Scenario: <Ai> <Làm gì> <Để đạt kết quả gì>
|
|
|
23
23
|
### Ví dụ
|
|
24
24
|
```gherkin
|
|
25
25
|
Scenario: User logs in successfully with valid credentials
|
|
26
|
-
Given User
|
|
26
|
+
Given User is on [Login] page
|
|
27
27
|
When User fill [Email] field with {{valid_email}}
|
|
28
28
|
And User fill [Password] field with {{valid_password}}
|
|
29
29
|
And User click [Submit] button
|
|
@@ -36,13 +36,36 @@ Scenario: User logs in successfully with valid credentials
|
|
|
36
36
|
|
|
37
37
|
### Định dạng
|
|
38
38
|
```
|
|
39
|
-
|
|
39
|
+
[Keyword] User <Action> [Target Name] <Target Type> <with {{Value}}> <is State>
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
### 17 Mẫu Pattern
|
|
43
|
+
|
|
44
|
+
| # | Mẫu | Ví dụ |
|
|
45
|
+
|---|------|-------|
|
|
46
|
+
| ① | Hành động đơn giản | `click [Submit] button` |
|
|
47
|
+
| ② | Hành động với dữ liệu | `fill [Email] field with {{email}}` |
|
|
48
|
+
| ③ | Xác nhận trạng thái | `see [Submit] button is disabled` |
|
|
49
|
+
| ④ | Trạng thái với dữ liệu | `see [Panel] dialog with {{title}} is hidden` |
|
|
50
|
+
| ⑤ | Hai đối tượng (kéo thả) | `drag [Card] to [Column]` |
|
|
51
|
+
| ⑥ | Phím toàn cục | `press Enter key` |
|
|
52
|
+
| ⑦ | Phím trên đối tượng | `press Enter on [Search] field` |
|
|
53
|
+
| ⑧ | Chờ thời gian | `wait for 3 seconds` |
|
|
54
|
+
| ⑨ | Chờ element | `wait for [Loader] spinner is hidden` |
|
|
55
|
+
| ⑩ | Cuộn trang | `scroll to [Footer] section` |
|
|
56
|
+
| ⑪ | Chuyển frame | `switch to [Payment] frame` |
|
|
57
|
+
| ⑫ | Có (số lượng/thuộc tính) | `see [Avatar] image has {{src}}` |
|
|
58
|
+
| ⑬ | Ô bảng theo chỉ số hàng | `see [Users] table row 1 [Name] cell {{name}}` |
|
|
59
|
+
| ⑭ | Ô bảng theo bộ lọc hàng | `see [Users] table row {{filter}} [Status] cell {{status}}` |
|
|
60
|
+
| ⑮ | Hành động trong hàng bảng | `click [Edit] button in [Users] table row {{filter}}` |
|
|
61
|
+
| ⑯ | Điều hướng | `is on [Dashboard] page` / `navigate to [Settings] page` |
|
|
62
|
+
| ⑰ | Xác nhận văn bản | `see [Title] heading contains {{text}}` |
|
|
63
|
+
|
|
42
64
|
### Quy tắc
|
|
43
65
|
- ✅ `Actor` + `Action` là **bắt buộc**
|
|
44
|
-
- ✅ `Target` là **bắt buộc** với hầu hết action (trừ `
|
|
66
|
+
- ✅ `Target` là **bắt buộc** với hầu hết action (trừ `wait for` timeout)
|
|
45
67
|
- ✅ `with {{Value}}` chỉ dùng khi action cần dữ liệu
|
|
68
|
+
- ✅ `is State` để xác nhận trạng thái element
|
|
46
69
|
- ❌ Không viết tự do (free-text)
|
|
47
70
|
- ❌ Không dùng từ đồng nghĩa
|
|
48
71
|
|
|
@@ -54,6 +77,7 @@ Scenario: User logs in successfully with valid credentials
|
|
|
54
77
|
| Action | ✅ Luôn luôn | `click`, `fill`, `see` |
|
|
55
78
|
| Target | ✅ Hầu hết | `[Login] button` |
|
|
56
79
|
| Value | ⚠️ Khi cần | `{{valid_email}}` |
|
|
80
|
+
| State | ⚠️ Khi cần | `is disabled` |
|
|
57
81
|
|
|
58
82
|
---
|
|
59
83
|
|
|
@@ -69,6 +93,7 @@ Scenario: User logs in successfully with valid credentials
|
|
|
69
93
|
|
|
70
94
|
### Ví dụ
|
|
71
95
|
```gherkin
|
|
96
|
+
✅ User is on [Login] page
|
|
72
97
|
✅ User click [Submit] button
|
|
73
98
|
❌ System sends verification email
|
|
74
99
|
❌ Backend validates password
|
|
@@ -81,28 +106,44 @@ Scenario: User logs in successfully with valid credentials
|
|
|
81
106
|
|
|
82
107
|
## 4️⃣ Action (Hành động)
|
|
83
108
|
|
|
84
|
-
### Các Action được phép (
|
|
109
|
+
### Các Action được phép (20 loại)
|
|
85
110
|
|
|
86
111
|
| Action | Trường hợp sử dụng | Ví dụ |
|
|
87
112
|
|--------|----------|---------|
|
|
88
|
-
| `open` | Điều hướng đến page/screen | `User open [Login] page` |
|
|
89
113
|
| `click` | Click button/link/element | `User click [Submit] button` |
|
|
90
114
|
| `fill` | Nhập text vào input field | `User fill [Email] field with {{email}}` |
|
|
91
|
-
| `select` | Chọn option từ dropdown
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
115
|
+
| `select` | Chọn option từ dropdown | `User select [Country] dropdown with {{country}}` |
|
|
116
|
+
| `check` | Đánh dấu checkbox | `User check [Remember me] checkbox` |
|
|
117
|
+
| `uncheck` | Bỏ đánh dấu checkbox | `User uncheck [Newsletter] checkbox` |
|
|
118
|
+
| `toggle` | Bật/tắt switch | `User toggle [Dark mode] switch` |
|
|
119
|
+
| `upload` | Tải file lên | `User upload [Avatar] file with {{avatar_path}}` |
|
|
120
|
+
| `hover` | Di chuột lên element | `User hover [Info] icon` |
|
|
121
|
+
| `drag` | Kéo thả element | `User drag [Card] to [Column]` |
|
|
122
|
+
| `clear` | Xóa nội dung input | `User clear [Search] field` |
|
|
123
|
+
| `see` | Verify hiển thị / trạng thái | `User see [Error] message` |
|
|
124
|
+
| `press` | Nhấn phím bàn phím | `User press Enter key` |
|
|
125
|
+
| `expand` | Mở rộng element | `User expand [Details] row` |
|
|
126
|
+
| `collapse` | Thu gọn element | `User collapse [Details] row` |
|
|
127
|
+
| `double click` | Double click element | `User double click [Cell] cell` |
|
|
128
|
+
| `scroll to` | Cuộn đến element | `User scroll to [Footer] section` |
|
|
129
|
+
| `switch to` | Chuyển sang frame | `User switch to [Payment] frame` |
|
|
130
|
+
| `wait for` | Đợi element/điều kiện | `User wait for [Loader] spinner` |
|
|
131
|
+
| `is on` | Xác nhận đang ở trang | `User is on [Login] page` |
|
|
132
|
+
| `navigate to` | Điều hướng đến trang | `User navigate to [Settings] page` |
|
|
133
|
+
|
|
134
|
+
> **Ghi chú**: `is on` và `navigate to` đều dùng cho điều hướng. `is on` là dạng ưu tiên cho step `Given`.
|
|
94
135
|
|
|
95
136
|
### Quy tắc
|
|
96
137
|
- ✅ Sử dụng **động từ chính xác** từ danh sách trên
|
|
97
|
-
- ❌ Không dùng từ đồng nghĩa: `type`, `enter`, `input`, `
|
|
138
|
+
- ❌ Không dùng từ đồng nghĩa: `type`, `enter`, `input`, `verify`, `expect`, v.v.
|
|
98
139
|
- ❌ Không dùng action kết hợp: `click and wait`, `fill and submit`
|
|
99
140
|
|
|
100
141
|
### Anti-patterns (Mẫu sai)
|
|
101
142
|
```gherkin
|
|
102
|
-
❌ User types {{password}} into [Password] field
|
|
103
|
-
❌ User presses [Enter] key
|
|
104
|
-
❌ User checks [Terms] checkbox
|
|
105
|
-
❌ User verifies [Success] message appears
|
|
143
|
+
❌ User types {{password}} into [Password] field → Dùng: User fill [Password] field with {{password}}
|
|
144
|
+
❌ User presses [Enter] key → Dùng: User press Enter key
|
|
145
|
+
❌ User checks [Terms] checkbox → Dùng: User check [Terms] checkbox
|
|
146
|
+
❌ User verifies [Success] message appears → Dùng: User see [Success] message
|
|
106
147
|
```
|
|
107
148
|
|
|
108
149
|
---
|
|
@@ -114,20 +155,76 @@ Scenario: User logs in successfully with valid credentials
|
|
|
114
155
|
[name] <element type>
|
|
115
156
|
```
|
|
116
157
|
|
|
117
|
-
### Các loại Element
|
|
158
|
+
### Các loại Element (40+)
|
|
159
|
+
|
|
160
|
+
#### Tương tác cơ bản
|
|
161
|
+
|
|
162
|
+
| Loại Element | Khi nào dùng | Ví dụ |
|
|
163
|
+
|--------------|-------------|---------|
|
|
164
|
+
| `button` | Nút có thể click | `[Submit] button` |
|
|
165
|
+
| `link` | Hyperlink | `[Forgot password] link` |
|
|
166
|
+
| `field` | Text input | `[Email] field` |
|
|
167
|
+
| `textarea` | Text area nhiều dòng | `[Message] textarea` |
|
|
168
|
+
| `heading` | Tiêu đề (h1-h6) | `[Welcome] heading` |
|
|
169
|
+
| `text` | Nội dung text | `[Price] text` |
|
|
170
|
+
| `image` | Hình ảnh | `[Avatar] image` |
|
|
171
|
+
| `icon` | Icon/hình ảnh nhỏ | `[Close] icon` |
|
|
172
|
+
|
|
173
|
+
#### Form controls
|
|
118
174
|
|
|
119
175
|
| Loại Element | Khi nào dùng | Ví dụ |
|
|
120
176
|
|--------------|-------------|---------|
|
|
121
|
-
| `field` | Text input, textarea | `[Email] field`, `[Password] field` |
|
|
122
|
-
| `button` | Button có thể click | `[Submit] button`, `[Cancel] button` |
|
|
123
177
|
| `checkbox` | Checkbox input | `[Remember me] checkbox` |
|
|
124
|
-
| `dropdown` | Select element | `[Country] dropdown` |
|
|
125
178
|
| `radio` | Radio button | `[Payment method] radio` |
|
|
126
|
-
| `
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
179
|
+
| `switch` | Toggle switch | `[Dark mode] switch` |
|
|
180
|
+
| `dropdown` | Select/combobox | `[Country] dropdown` |
|
|
181
|
+
| `option` | Option trong dropdown | `[Vietnam] option` |
|
|
182
|
+
| `slider` | Thanh trượt | `[Volume] slider` |
|
|
183
|
+
| `uploader` | Vùng upload file | `[Document] uploader` |
|
|
184
|
+
| `file` | File input | `[Avatar] file` |
|
|
185
|
+
|
|
186
|
+
#### Overlay & navigation
|
|
187
|
+
|
|
188
|
+
| Loại Element | Khi nào dùng | Ví dụ |
|
|
189
|
+
|--------------|-------------|---------|
|
|
190
|
+
| `dialog` | Hộp thoại | `[Confirm] dialog` |
|
|
191
|
+
| `modal` | Modal overlay | `[Edit] modal` |
|
|
192
|
+
| `menu` | Menu dropdown | `[Actions] menu` |
|
|
193
|
+
| `menuitem` | Mục trong menu | `[Delete] menuitem` |
|
|
194
|
+
| `tab` | Tab header | `[Settings] tab` |
|
|
195
|
+
| `tabpanel` | Nội dung tab | `[General] tabpanel` |
|
|
196
|
+
| `tooltip` | Tooltip | `[Help] tooltip` |
|
|
197
|
+
| `alert` | Thông báo | `[Error] alert` |
|
|
198
|
+
|
|
199
|
+
#### Cấu trúc & layout
|
|
200
|
+
|
|
201
|
+
| Loại Element | Khi nào dùng | Ví dụ |
|
|
202
|
+
|--------------|-------------|---------|
|
|
203
|
+
| `list` | Danh sách | `[Results] list` |
|
|
204
|
+
| `listitem` | Mục trong danh sách | `[Item 1] listitem` |
|
|
205
|
+
| `table` | Bảng dữ liệu | `[Users] table` |
|
|
206
|
+
| `row` | Hàng trong bảng | `[Row 1] row` |
|
|
207
|
+
| `cell` | Ô trong bảng | `[Name] cell` |
|
|
208
|
+
| `column` | Cột | `[Status] column` |
|
|
209
|
+
| `columnheader` | Tiêu đề cột | `[Name] columnheader` |
|
|
210
|
+
| `region` | Vùng landmark | `[Main] region` |
|
|
211
|
+
| `section` | Section | `[Footer] section` |
|
|
212
|
+
| `nav` | Thanh navigation | `[Main] nav` |
|
|
213
|
+
| `banner` | Banner | `[Top] banner` |
|
|
214
|
+
| `header` | Header | `[Page] header` |
|
|
215
|
+
| `footer` | Footer | `[Page] footer` |
|
|
216
|
+
|
|
217
|
+
#### Đặc biệt
|
|
218
|
+
|
|
219
|
+
| Loại Element | Khi nào dùng | Ví dụ |
|
|
220
|
+
|--------------|-------------|---------|
|
|
221
|
+
| `page` | Toàn bộ trang | `[Login] page` |
|
|
222
|
+
| `spinner` | Loading indicator | `[Loader] spinner` |
|
|
223
|
+
| `progressbar` | Thanh tiến trình | `[Upload] progressbar` |
|
|
224
|
+
| `tree` | Cấu trúc cây | `[Files] tree` |
|
|
225
|
+
| `treeitem` | Mục trong cây | `[Folder] treeitem` |
|
|
226
|
+
| `frame` | Iframe | `[Payment] frame` |
|
|
227
|
+
| `iframe` | Iframe (tương đương frame) | `[Embed] iframe` |
|
|
131
228
|
|
|
132
229
|
### Quy tắc
|
|
133
230
|
- ✅ Dùng **tên có nghĩa nghiệp vụ/UI**, không dùng selector kỹ thuật
|
|
@@ -140,8 +237,9 @@ Scenario: User logs in successfully with valid credentials
|
|
|
140
237
|
```gherkin
|
|
141
238
|
✅ User click [Login] button
|
|
142
239
|
✅ User fill [Email address] field with {{email}}
|
|
143
|
-
✅ User
|
|
144
|
-
✅ User
|
|
240
|
+
✅ User check [Remember me] checkbox
|
|
241
|
+
✅ User toggle [Dark mode] switch
|
|
242
|
+
✅ User see [Welcome back] heading
|
|
145
243
|
|
|
146
244
|
❌ User click [#submit-btn] button
|
|
147
245
|
❌ User fill [input.email-field] field
|
|
@@ -192,7 +290,26 @@ expired_otp: "000000"
|
|
|
192
290
|
## 7️⃣ Assertion (Xác nhận)
|
|
193
291
|
|
|
194
292
|
### Động từ
|
|
195
|
-
- **
|
|
293
|
+
- **Chính**: `see` (xác nhận hiển thị / trạng thái)
|
|
294
|
+
|
|
295
|
+
### States (Trạng thái)
|
|
296
|
+
|
|
297
|
+
Thêm `is <state>` vào cuối step `see` hoặc `wait for` để xác nhận trạng thái:
|
|
298
|
+
|
|
299
|
+
| State | Mô tả | Ví dụ |
|
|
300
|
+
|-------|--------|-------|
|
|
301
|
+
| `hidden` | Element bị ẩn | `User see [Modal] dialog is hidden` |
|
|
302
|
+
| `visible` | Element hiển thị | `User see [Banner] section is visible` |
|
|
303
|
+
| `disabled` | Element bị vô hiệu | `User see [Submit] button is disabled` |
|
|
304
|
+
| `enabled` | Element có thể tương tác | `User see [Submit] button is enabled` |
|
|
305
|
+
| `checked` | Checkbox/radio được chọn | `User see [Terms] checkbox is checked` |
|
|
306
|
+
| `unchecked` | Checkbox/radio chưa chọn | `User see [Terms] checkbox is unchecked` |
|
|
307
|
+
| `focused` | Element đang được focus | `User see [Email] field is focused` |
|
|
308
|
+
| `empty` | Element rỗng | `User see [Search] field is empty` |
|
|
309
|
+
| `loading` | Element đang tải | `User see [Content] section is loading` |
|
|
310
|
+
| `selected` | Element được chọn | `User see [Tab 1] tab is selected` |
|
|
311
|
+
| `sorted ascending` | Sắp xếp tăng dần | `User see [Name] columnheader is sorted ascending` |
|
|
312
|
+
| `sorted descending` | Sắp xếp giảm dần | `User see [Name] columnheader is sorted descending` |
|
|
196
313
|
|
|
197
314
|
### Quy tắc
|
|
198
315
|
- ✅ Assertion **chỉ xuất hiện trong `Then`**
|
|
@@ -201,26 +318,101 @@ expired_otp: "000000"
|
|
|
201
318
|
- ❌ Không mô tả hành động của user
|
|
202
319
|
- ❌ Không dùng từ đồng nghĩa: `verify`, `expect`, `check`, `observe`, `validate`
|
|
203
320
|
|
|
321
|
+
### Các mẫu Assertion
|
|
322
|
+
|
|
323
|
+
| Mẫu | Trường hợp sử dụng | Ví dụ |
|
|
324
|
+
|---------|----------|---------|
|
|
325
|
+
| Hiển thị đơn giản | Element có thể nhìn thấy | `User see [Login] button` |
|
|
326
|
+
| Hiển thị với giá trị | Element chứa text cụ thể | `User see [Welcome] heading with {{username}}` |
|
|
327
|
+
| Xác nhận trạng thái | Element ở trạng thái nhất định | `User see [Submit] button is disabled` |
|
|
328
|
+
| Trạng thái với giá trị | Element có giá trị và trạng thái | `User see [Panel] dialog with {{title}} is hidden` |
|
|
329
|
+
| Chứa text | Element chứa text | `User see [Title] heading contains {{text}}` |
|
|
330
|
+
| Có thuộc tính | Element có thuộc tính/giá trị | `User see [Avatar] image has {{src}}` |
|
|
331
|
+
| Verify page | Xác nhận điều hướng | `User is on [Dashboard] page` |
|
|
332
|
+
| Chờ ẩn | Đợi element biến mất | `User wait for [Loader] spinner is hidden` |
|
|
333
|
+
|
|
334
|
+
### Xác nhận bảng (Table Assertions)
|
|
335
|
+
|
|
336
|
+
| Mẫu | Ví dụ |
|
|
337
|
+
|------|-------|
|
|
338
|
+
| Ô theo chỉ số hàng | `User see [Users] table row 1 [Name] cell {{name}}` |
|
|
339
|
+
| Ô theo bộ lọc hàng | `User see [Users] table row {{filter}} [Status] cell {{status}}` |
|
|
340
|
+
| Hành động trong hàng | `User click [Edit] button in [Users] table row {{filter}}` |
|
|
341
|
+
| Đếm số hàng | `User see [Users] table has {{count}} rows` |
|
|
342
|
+
| Bảng rỗng | `User see [Users] table is empty` |
|
|
343
|
+
| Có cột | `User see [Users] table has [Email] column` |
|
|
344
|
+
|
|
204
345
|
### Ví dụ
|
|
205
346
|
```gherkin
|
|
206
|
-
✅ Then User see [Error]
|
|
347
|
+
✅ Then User see [Error] alert
|
|
207
348
|
✅ And User see [Dashboard] page
|
|
208
|
-
✅ And User see [Welcome]
|
|
209
|
-
✅ And User see [
|
|
349
|
+
✅ And User see [Welcome] heading with {{username}}
|
|
350
|
+
✅ And User see [Submit] button is disabled
|
|
351
|
+
✅ And User see [Terms] checkbox is checked
|
|
352
|
+
✅ And User see [Users] table row 1 [Name] cell {{name}}
|
|
210
353
|
|
|
211
354
|
❌ Then User verifies [Error] message
|
|
212
355
|
❌ Then System displays [Dashboard] page
|
|
213
356
|
❌ Then [Success] message appears
|
|
214
357
|
```
|
|
215
358
|
|
|
216
|
-
|
|
359
|
+
---
|
|
217
360
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
|
223
|
-
|
|
361
|
+
## 🏷️ Tags
|
|
362
|
+
|
|
363
|
+
### Scenario Tags
|
|
364
|
+
|
|
365
|
+
| Tag | Mô tả |
|
|
366
|
+
|-----|-------|
|
|
367
|
+
| `@auth:<role>` | Sử dụng trạng thái auth Playwright cho role đã cho |
|
|
368
|
+
| `@no-auth` | Tắt authentication kế thừa |
|
|
369
|
+
| `@steps:<name>` | Đánh dấu scenario là khối step tái sử dụng |
|
|
370
|
+
| `@extend:<name>` | Chèn step từ khối `@steps` trước step hiện tại |
|
|
371
|
+
| `@manual` | Bỏ qua scenario khi sinh code |
|
|
372
|
+
|
|
373
|
+
Tags có thể áp dụng ở **cấp Feature** (kế thừa cho tất cả scenario) hoặc **cấp Scenario** (ghi đè Feature).
|
|
374
|
+
|
|
375
|
+
**Thứ tự ưu tiên Auth**: Scenario kế thừa `@auth` > Scenario gốc `@auth` > Feature `@auth`
|
|
376
|
+
|
|
377
|
+
### Step tái sử dụng (@steps / @extend)
|
|
378
|
+
|
|
379
|
+
Dùng `@steps:<name>` để định nghĩa khối tái sử dụng và `@extend:<name>` để kế thừa:
|
|
380
|
+
|
|
381
|
+
```gherkin
|
|
382
|
+
@auth:user @steps:open-dialog
|
|
383
|
+
Scenario: Open kudos dialog
|
|
384
|
+
Given User is on [Kudo] page
|
|
385
|
+
When User click [Notifications] button
|
|
386
|
+
And User click [Write Kudos] menuitem
|
|
387
|
+
Then User see [Panel] dialog with {{kudo_title}}
|
|
388
|
+
|
|
389
|
+
@auth:user @extend:open-dialog
|
|
390
|
+
Scenario: User sends a thank you message
|
|
391
|
+
Given User is on [Panel] dialog with {{kudo_title}}
|
|
392
|
+
When User click [Search] button
|
|
393
|
+
And User fill [Search] field with {{teammate_name}}
|
|
394
|
+
And User click [Teammate] row with {{teammate_name}}
|
|
395
|
+
And User fill [Title] field with {{teammate_title}}
|
|
396
|
+
And User fill [Message] textarea with {{teammate_message}}
|
|
397
|
+
And User click [Hashtag] button with {{hashtag_1}}
|
|
398
|
+
And User click [Send] button
|
|
399
|
+
And User wait for [Panel] dialog with {{kudo_title}} is hidden
|
|
400
|
+
Then User see [Panel] modal with {{kudo_title}} is hidden
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**Hành vi:**
|
|
404
|
+
- Scenario `@steps` sinh test riêng VÀ đăng ký step để tái sử dụng
|
|
405
|
+
- Scenario `@extend` nhận step gốc chèn vào đầu — test hoàn toàn độc lập
|
|
406
|
+
- Nhiều scenario có thể extend cùng một khối `@steps`
|
|
407
|
+
- Code sinh ra có comment phân đoạn: `// [from @steps:<name>]`
|
|
408
|
+
|
|
409
|
+
### Annotation cấp Step (cú pháp comment)
|
|
410
|
+
|
|
411
|
+
| Annotation | Hiệu lực |
|
|
412
|
+
|-----------|--------|
|
|
413
|
+
| `# @ignore` | Bỏ qua step này khi sinh code |
|
|
414
|
+
| `# @ignore-testcase` | Bỏ qua toàn bộ scenario khi sinh code |
|
|
415
|
+
| `# @skip-production` | Bỏ qua step trong môi trường production |
|
|
224
416
|
|
|
225
417
|
---
|
|
226
418
|
|
|
@@ -230,30 +422,47 @@ expired_otp: "000000"
|
|
|
230
422
|
Feature: User Authentication
|
|
231
423
|
Path: /auth/login
|
|
232
424
|
|
|
425
|
+
@auth:admin
|
|
233
426
|
Scenario: User logs in successfully with valid credentials
|
|
234
|
-
Given User
|
|
427
|
+
Given User is on [Login] page
|
|
235
428
|
When User fill [Email] field with {{valid_email}}
|
|
236
429
|
And User fill [Password] field with {{valid_password}}
|
|
237
|
-
And User
|
|
430
|
+
And User check [Remember me] checkbox
|
|
238
431
|
And User click [Submit] button
|
|
239
432
|
Then User see [Dashboard] page
|
|
240
|
-
And User see [Welcome]
|
|
433
|
+
And User see [Welcome] heading with {{username}}
|
|
241
434
|
|
|
435
|
+
@no-auth
|
|
242
436
|
Scenario: User fails to login with invalid credentials
|
|
243
|
-
Given User
|
|
437
|
+
Given User is on [Login] page
|
|
244
438
|
When User fill [Email] field with {{invalid_email}}
|
|
245
439
|
And User fill [Password] field with {{invalid_password}}
|
|
246
440
|
And User click [Submit] button
|
|
247
|
-
Then User see [Error]
|
|
441
|
+
Then User see [Error] alert
|
|
442
|
+
And User see [Submit] button is enabled
|
|
248
443
|
And User see [Login] page
|
|
249
444
|
|
|
250
|
-
|
|
251
|
-
|
|
445
|
+
@no-auth
|
|
446
|
+
Scenario: User registers with preferred language
|
|
447
|
+
Given User is on [Registration] page
|
|
252
448
|
When User fill [Email] field with {{new_email}}
|
|
253
449
|
And User fill [Password] field with {{new_password}}
|
|
254
450
|
And User select [Language] dropdown with {{preferred_language}}
|
|
451
|
+
And User check [Terms] checkbox
|
|
255
452
|
And User click [Sign up] button
|
|
256
453
|
Then User see [Verification] page
|
|
454
|
+
|
|
455
|
+
Scenario: User manages settings with various controls
|
|
456
|
+
Given User is on [Settings] page
|
|
457
|
+
When User toggle [Dark mode] switch
|
|
458
|
+
And User select [Timezone] dropdown with {{timezone}}
|
|
459
|
+
And User upload [Avatar] file with {{avatar_path}}
|
|
460
|
+
And User press Enter key
|
|
461
|
+
And User scroll to [Save] section
|
|
462
|
+
And User click [Save] button
|
|
463
|
+
And User wait for [Loader] spinner is hidden
|
|
464
|
+
Then User see [Success] alert
|
|
465
|
+
And User see [Dark mode] switch is checked
|
|
257
466
|
```
|
|
258
467
|
|
|
259
468
|
---
|
|
@@ -261,10 +470,12 @@ Feature: User Authentication
|
|
|
261
470
|
## ✅ Tham Khảo Nhanh
|
|
262
471
|
|
|
263
472
|
### Nên làm ✅
|
|
264
|
-
- Dùng **
|
|
473
|
+
- Dùng **20 action chuẩn** từ danh sách action
|
|
265
474
|
- Dùng **`User`** là actor duy nhất
|
|
266
475
|
- Dùng định dạng **`[Target] <element type>`**
|
|
267
476
|
- Dùng **`{{snake_case}}`** cho giá trị
|
|
477
|
+
- Dùng **`is <state>`** cho xác nhận trạng thái
|
|
478
|
+
- Dùng **tags** (`@auth`, `@steps`, `@extend`) để quản lý scenario
|
|
268
479
|
- **1 Scenario = 1 flow nghiệp vụ**
|
|
269
480
|
- **1 Step = 1 action hoặc assertion**
|
|
270
481
|
|
|
@@ -274,7 +485,7 @@ Feature: User Authentication
|
|
|
274
485
|
- Không dùng selector kỹ thuật (CSS/XPath/ID)
|
|
275
486
|
- Không hard-code dữ liệu trong step
|
|
276
487
|
- Không dùng action kết hợp
|
|
277
|
-
- Không dùng `verify`/`check`/`expect`
|
|
488
|
+
- Không dùng `verify`/`check`/`expect` thay cho `see`
|
|
278
489
|
|
|
279
490
|
---
|
|
280
491
|
|
|
@@ -282,22 +493,21 @@ Feature: User Authentication
|
|
|
282
493
|
|
|
283
494
|
| Lợi ích | Mô tả |
|
|
284
495
|
|---------|-------------|
|
|
285
|
-
| **Nhất quán** | Cùng
|
|
496
|
+
| **Nhất quán** | Cùng 17 mẫu pattern cho tất cả test case |
|
|
286
497
|
| **Dễ đọc** | Người không kỹ thuật cũng hiểu được |
|
|
287
498
|
| **Dễ bảo trì** | Dễ cập nhật khi UI thay đổi |
|
|
288
|
-
| **Sẵn sàng tự động** | Ánh xạ trực tiếp sang code
|
|
499
|
+
| **Sẵn sàng tự động** | Ánh xạ trực tiếp sang Playwright code |
|
|
289
500
|
| **Không mơ hồ** | Mỗi step chỉ có một cách hiểu |
|
|
290
|
-
| **Hỗ trợ công cụ** | Tương thích với framework Sungen |
|
|
501
|
+
| **Hỗ trợ công cụ** | Tương thích với framework Sungen v2 |
|
|
502
|
+
| **Tái sử dụng** | `@steps` / `@extend` giúp tái sử dụng flow |
|
|
291
503
|
|
|
292
504
|
---
|
|
293
505
|
|
|
294
506
|
## 📚 Tài Liệu Liên Quan
|
|
295
|
-
- [
|
|
296
|
-
- [Hướng dẫn Validation](validate.md) - Validate file Gherkin
|
|
297
|
-
- [Thiết lập Auth](makeauth.md) - Cấu hình trạng thái authentication
|
|
507
|
+
- [Từ điển Gherkin](../gherkin-dictionary.md) — Ngữ pháp đầy đủ, 17 mẫu pattern, quy tắc compiler
|
|
298
508
|
|
|
299
509
|
---
|
|
300
510
|
|
|
301
|
-
**Phiên bản**:
|
|
302
|
-
**Trạng thái**: Final
|
|
303
|
-
**Cập nhật lần cuối**:
|
|
511
|
+
**Phiên bản**: 2.0
|
|
512
|
+
**Trạng thái**: Final
|
|
513
|
+
**Cập nhật lần cuối**: 19 tháng 3, 2026
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sungen",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"automation",
|
|
26
26
|
"qa"
|
|
27
27
|
],
|
|
28
|
-
"author": "
|
|
28
|
+
"author": "ee team (engineer-excellence) — Sun Asterisk",
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"engines": {
|
|
31
31
|
"node": ">=18.0.0"
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -4,10 +4,11 @@ export function registerInitCommand(program: Command): void {
|
|
|
4
4
|
program
|
|
5
5
|
.command('init [projectName]')
|
|
6
6
|
.description('Initialize Sungen project (optionally create and initialize in a new project folder)')
|
|
7
|
-
.
|
|
7
|
+
.requiredOption('--base-url <url>', 'Base URL for Playwright tests (e.g. https://your-app.com)')
|
|
8
|
+
.action(async (projectName: string | undefined, options: { baseUrl: string }) => {
|
|
8
9
|
try {
|
|
9
10
|
const { ProjectInitializer } = require('../../orchestrator/project-initializer');
|
|
10
|
-
const initializer = new ProjectInitializer();
|
|
11
|
+
const initializer = new ProjectInitializer(options.baseUrl);
|
|
11
12
|
await initializer.initialize(projectName);
|
|
12
13
|
} catch (error) {
|
|
13
14
|
console.error('❌ Init failed:', error);
|
package/src/cli/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
await page.getByText('{{escapeQuotes selectorValue}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{#if (
|
|
1
|
+
await page.getByText('{{escapeQuotes selectorValue}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}.{{action}}();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
await page.waitForLoadState('networkidle');
|
|
2
2
|
{{#if name}}
|
|
3
|
-
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (
|
|
3
|
+
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeDisabled();
|
|
4
4
|
{{else}}
|
|
5
|
-
await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (
|
|
5
|
+
await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeDisabled();
|
|
6
6
|
{{/if}}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
await page.waitForLoadState('networkidle');
|
|
2
|
-
await expect(page.getByText('{{escapeQuotes selectorRef}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{#if (
|
|
2
|
+
await expect(page.getByText('{{escapeQuotes selectorRef}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeDisabled();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
await page.waitForLoadState('networkidle');
|
|
2
2
|
{{#if name}}
|
|
3
|
-
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (
|
|
3
|
+
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeHidden();
|
|
4
4
|
{{else}}
|
|
5
|
-
await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (
|
|
5
|
+
await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeHidden();
|
|
6
6
|
{{/if}}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
await page.waitForLoadState('networkidle');
|
|
2
|
-
await expect(page.getByText('{{escapeQuotes selectorRef}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{#if (
|
|
2
|
+
await expect(page.getByText('{{escapeQuotes selectorRef}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeHidden();
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
await page.waitForLoadState('networkidle');
|
|
2
2
|
{{#if name}}
|
|
3
3
|
{{#if dataValue}}
|
|
4
|
-
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (
|
|
4
|
+
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
|
|
5
5
|
{{else}}
|
|
6
|
-
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{#if (
|
|
6
|
+
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
|
|
7
7
|
{{/if}}
|
|
8
8
|
{{else}}
|
|
9
|
-
await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (
|
|
9
|
+
await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
|
|
10
10
|
{{/if}}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
await page.waitForLoadState('networkidle');
|
|
2
|
-
await expect(page.getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{#if (
|
|
2
|
+
await expect(page.getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
await page.waitForLoadState('networkidle');
|
|
2
|
-
await expect(page.getByText('{{escapeQuotes selectorValue}}').filter({ hasText: /.*{{escapeRegex dataValue}}$/ }){{#if (
|
|
2
|
+
await expect(page.getByText('{{escapeQuotes selectorValue}}').filter({ hasText: /.*{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
|
package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{{#if (
|
|
1
|
+
{{#if (gte nth 0)}}.nth({{nth}}){{/if}}
|