@sun-asterisk/sungen 2.1.1 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -51
- package/dist/cli/index.js +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/count-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-filter-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/empty-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/enabled-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/focused-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-dialog-heading-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-filter-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/is-hidden-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/list-item-count-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/loading-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/page-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/sorted-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-filter.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-index.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-column-exists.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-dialog-heading-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-locator-variable-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +1 -1
- package/dist/generators/test-generator/code-generator.d.ts +4 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +19 -0
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/patterns/navigation-patterns.js +2 -2
- package/dist/generators/test-generator/patterns/navigation-patterns.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +15 -8
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +26 -197
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +8 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +68 -4
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +4 -3
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md +11 -46
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +11 -46
- package/dist/orchestrator/templates/ai-instructions/claude-config.md +9 -8
- package/dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +29 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -2
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +206 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +19 -21
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +256 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +14 -17
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-tc.md +16 -47
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +16 -47
- package/dist/orchestrator/templates/ai-instructions/copilot-config.md +8 -7
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +56 -0
- package/{src/orchestrator/templates/ai-instructions/copilot-skill-gherkin-syntax.md → dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md} +5 -5
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +206 -0
- package/dist/orchestrator/templates/ai-instructions/{copilot-skill-selector-keys.md → github-skill-sungen-selector-keys.md} +22 -24
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +257 -0
- package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
- package/dist/orchestrator/templates/playwright.config.js +3 -1
- package/dist/orchestrator/templates/playwright.config.js.map +1 -1
- package/dist/orchestrator/templates/playwright.config.ts +3 -1
- package/dist/orchestrator/templates/readme.md +78 -101
- package/dist/orchestrator/templates/specs-base.d.ts +4 -0
- package/dist/orchestrator/templates/specs-base.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-base.js +70 -0
- package/dist/orchestrator/templates/specs-base.js.map +1 -0
- package/dist/orchestrator/templates/specs-base.ts +73 -0
- package/package.json +1 -1
- package/src/cli/index.ts +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/count-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-filter-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/empty-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/enabled-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/focused-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-dialog-heading-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-filter-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/is-hidden-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/list-item-count-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/loading-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/page-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/sorted-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-filter.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-index.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-column-exists.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-dialog-heading-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-locator-variable-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +1 -1
- package/src/generators/test-generator/code-generator.ts +21 -0
- package/src/generators/test-generator/patterns/navigation-patterns.ts +2 -2
- package/src/generators/test-generator/utils/selector-resolver.ts +27 -204
- package/src/orchestrator/project-initializer.ts +84 -5
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +4 -3
- package/src/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md +11 -46
- package/src/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +11 -46
- package/src/orchestrator/templates/ai-instructions/claude-config.md +9 -8
- package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +29 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -2
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +206 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +19 -21
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +256 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +14 -17
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-tc.md +16 -47
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +16 -47
- package/src/orchestrator/templates/ai-instructions/copilot-config.md +8 -7
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +56 -0
- package/{dist/orchestrator/templates/ai-instructions/copilot-skill-gherkin-syntax.md → src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md} +5 -5
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +206 -0
- package/src/orchestrator/templates/ai-instructions/{copilot-skill-selector-keys.md → github-skill-sungen-selector-keys.md} +22 -24
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +257 -0
- package/src/orchestrator/templates/playwright.config.ts +3 -1
- package/src/orchestrator/templates/readme.md +78 -101
- package/src/orchestrator/templates/specs-base.ts +73 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-skill-error-mapping.md +0 -27
- package/src/orchestrator/templates/ai-instructions/copilot-skill-error-mapping.md +0 -27
|
@@ -17,11 +17,17 @@ sungen generate → compiles Gherkin + selectors + data → Playwright .spec.ts
|
|
|
17
17
|
│ ├── selectors/ # Element locator YAML mappings
|
|
18
18
|
│ └── test-data/ # Test data YAML values
|
|
19
19
|
├── specs/
|
|
20
|
-
│
|
|
21
|
-
|
|
20
|
+
│ └── generated/ # Auto-generated Playwright tests
|
|
21
|
+
├── .claude/
|
|
22
|
+
│ ├── commands/sungen/ # Claude Code slash commands
|
|
23
|
+
│ └── skills/ # Claude Code auto-loaded skills
|
|
22
24
|
├── .github/
|
|
23
|
-
│
|
|
24
|
-
└──
|
|
25
|
+
│ ├── copilot-instructions.md # AI rules for GitHub Copilot
|
|
26
|
+
│ └── prompts/ # Copilot slash commands
|
|
27
|
+
├── .vscode/
|
|
28
|
+
│ └── settings.json # Copilot auto-attach settings
|
|
29
|
+
├── CLAUDE.md # AI rules for Claude Code
|
|
30
|
+
└── playwright.config.ts # Playwright config (timeout: 10s, workers: 2)
|
|
25
31
|
```
|
|
26
32
|
|
|
27
33
|
## Workflow
|
|
@@ -32,45 +38,50 @@ Step 1 Step 2 Step 3
|
|
|
32
38
|
│ /add- │────────▶│ /make-tc │────────▶│ /make- │
|
|
33
39
|
│ screen │ │ │ │ test │
|
|
34
40
|
└──────────┘ └──────────┘ └──────────┘
|
|
35
|
-
Scaffold
|
|
36
|
-
directories
|
|
37
|
-
|
|
41
|
+
Scaffold Pick sections Generate
|
|
42
|
+
directories → design TCs selectors
|
|
43
|
+
→ generate → compile
|
|
44
|
+
.feature + → run tests
|
|
45
|
+
test-data → auto-fix
|
|
38
46
|
```
|
|
39
47
|
|
|
40
48
|
Use AI commands (Claude Code or GitHub Copilot) to drive the workflow:
|
|
41
49
|
|
|
42
50
|
### Step 1: Add a screen
|
|
43
51
|
|
|
44
|
-
| Claude Code | GitHub Copilot |
|
|
52
|
+
| Claude Code | GitHub Copilot (VS Code) |
|
|
45
53
|
|---|---|
|
|
46
|
-
| `/sungen:add-screen
|
|
54
|
+
| `/sungen:add-screen` | `/sungen-add-screen` |
|
|
47
55
|
|
|
48
56
|
Scaffolds `qa/screens/<name>/` with empty feature, selectors, and test-data files, then asks if you want to create test cases.
|
|
49
57
|
|
|
50
58
|
### Step 2: Create test cases
|
|
51
59
|
|
|
52
|
-
| Claude Code | GitHub Copilot |
|
|
60
|
+
| Claude Code | GitHub Copilot (VS Code) |
|
|
53
61
|
|---|---|
|
|
54
|
-
| `/sungen:make-tc login` |
|
|
62
|
+
| `/sungen:make-tc login` | `/sungen-make-tc login` |
|
|
55
63
|
|
|
56
|
-
AI acts as a **Senior QA Engineer**:
|
|
64
|
+
AI acts as a **Senior QA Engineer**:
|
|
65
|
+
1. Explores the live page via Playwright MCP (asks user to log in if auth needed)
|
|
66
|
+
2. Identifies screen sections → asks user which to focus on
|
|
67
|
+
3. Generates **20+ scenarios per viewpoint** (UI/UX, Validation, Logic, Security) for each section
|
|
68
|
+
4. Confirms test plan before generating `.feature` + `test-data.yaml`
|
|
57
69
|
|
|
58
70
|
### Step 3: Compile & run tests
|
|
59
71
|
|
|
60
|
-
| Claude Code | GitHub Copilot |
|
|
72
|
+
| Claude Code | GitHub Copilot (VS Code) |
|
|
61
73
|
|---|---|
|
|
62
|
-
| `/sungen:make-test login` |
|
|
74
|
+
| `/sungen:make-test login` | `/sungen-make-test login` |
|
|
63
75
|
|
|
64
|
-
AI acts as a **Senior Developer**:
|
|
76
|
+
AI acts as a **Senior Developer**:
|
|
77
|
+
1. Navigates to live page, takes snapshot, verifies DOM properties
|
|
78
|
+
2. Generates `selectors.yaml` from exact accessible names
|
|
79
|
+
3. Compiles Gherkin → Playwright `.spec.ts`
|
|
80
|
+
4. Runs tests, auto-fixes selectors on failure (up to 5 attempts)
|
|
65
81
|
|
|
66
|
-
### Auth setup
|
|
82
|
+
### Auth setup
|
|
67
83
|
|
|
68
|
-
If any page requires authentication,
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
sungen makeauth admin --url <baseURL>
|
|
72
|
-
# Opens browser → login manually → saves specs/.auth/admin.json
|
|
73
|
-
```
|
|
84
|
+
If any page requires authentication, the AI will ask you to **log in manually via the MCP browser** during Step 2 or Step 3. No separate auth command needed.
|
|
74
85
|
|
|
75
86
|
### Manual CLI commands
|
|
76
87
|
|
|
@@ -85,7 +96,24 @@ npx playwright test --ui # Interactive mode
|
|
|
85
96
|
|
|
86
97
|
---
|
|
87
98
|
|
|
88
|
-
##
|
|
99
|
+
## VS Code Copilot Setup
|
|
100
|
+
|
|
101
|
+
Add to `.vscode/settings.json` to auto-load Gherkin syntax when editing `.feature` files:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"github.copilot.chat.codeGeneration.instructions": [
|
|
106
|
+
{
|
|
107
|
+
"file": ".github/prompts/sungen-gherkin-syntax.prompt.md",
|
|
108
|
+
"applyTo": "**/*.feature"
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Gherkin Quick Reference
|
|
89
117
|
|
|
90
118
|
### Syntax
|
|
91
119
|
|
|
@@ -97,101 +125,50 @@ User <action> [<Target>] <type> with {{<Value>}}
|
|
|
97
125
|
- `{{Value}}` → test data reference → lookup in `test-data/*.yaml`
|
|
98
126
|
- `<type>` → element type: button, link, field, heading, text, etc.
|
|
99
127
|
|
|
100
|
-
###
|
|
101
|
-
|
|
102
|
-
#### Actions
|
|
103
|
-
|
|
104
|
-
| Pattern | Example |
|
|
105
|
-
|---|---|
|
|
106
|
-
| Simple click | `User click [Submit] button` |
|
|
107
|
-
| Click with data | `User click [Teammate] button with {{name}}` |
|
|
108
|
-
| Fill field | `User fill [Email] field with {{email}}` |
|
|
109
|
-
| Check/Uncheck | `User check [Remember me] checkbox` |
|
|
110
|
-
| Select dropdown | `User select [Country] dropdown with {{country}}` |
|
|
111
|
-
| Upload file | `User upload [Avatar] file with {{path}}` |
|
|
112
|
-
| Double click | `User double click [Cell] element` |
|
|
113
|
-
| Hover | `User hover [Info] icon` |
|
|
114
|
-
| Clear | `User clear [Search] field` |
|
|
115
|
-
|
|
116
|
-
#### Keyboard
|
|
117
|
-
|
|
118
|
-
| Pattern | Example |
|
|
119
|
-
|---|---|
|
|
120
|
-
| Global key | `User press Escape key` |
|
|
121
|
-
| Key on element | `User press Enter on [Search] field` |
|
|
122
|
-
|
|
123
|
-
#### Navigation
|
|
124
|
-
|
|
125
|
-
| Pattern | Example |
|
|
126
|
-
|---|---|
|
|
127
|
-
| Open page | `User is on [login] page` |
|
|
128
|
-
| See page (URL) | `User see [dashboard] page` |
|
|
129
|
-
|
|
130
|
-
#### Wait
|
|
131
|
-
|
|
132
|
-
| Pattern | Example |
|
|
133
|
-
|---|---|
|
|
134
|
-
| Wait timeout | `User wait for 3 seconds` |
|
|
135
|
-
| Wait element | `User wait for [Modal] dialog` |
|
|
136
|
-
| Wait hidden | `User wait for [Loading] spinner is hidden` |
|
|
137
|
-
| Wait with data | `User wait for [Dialog] dialog with {{title}}` |
|
|
138
|
-
|
|
139
|
-
#### Assertions
|
|
140
|
-
|
|
141
|
-
| Pattern | Example |
|
|
142
|
-
|---|---|
|
|
143
|
-
| See element | `User see [Welcome] heading` |
|
|
144
|
-
| See with value | `User see [Title] heading with {{title}}` |
|
|
145
|
-
| State check | `User see [Submit] button is disabled` |
|
|
146
|
-
| State + data | `User see [Panel] dialog with {{title}} is hidden` |
|
|
147
|
-
| Text contains | `User see [Message] text contains {{partial}}` |
|
|
148
|
-
|
|
149
|
-
States: `hidden`, `visible`, `disabled`, `enabled`, `checked`, `unchecked`, `focused`, `empty`
|
|
150
|
-
|
|
151
|
-
#### Table
|
|
128
|
+
### Key Patterns
|
|
152
129
|
|
|
153
130
|
| Pattern | Example |
|
|
154
131
|
|---|---|
|
|
155
|
-
|
|
|
156
|
-
|
|
|
157
|
-
|
|
|
158
|
-
|
|
|
159
|
-
|
|
|
160
|
-
|
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
| Pattern | Example |
|
|
167
|
-
|---|---|
|
|
168
|
-
| Scroll | `User scroll to [Footer] section` |
|
|
169
|
-
| Frame enter | `User switch to [Payment] frame` |
|
|
170
|
-
| Frame exit | `User switch to [main] frame` |
|
|
132
|
+
| Navigate | `User is on [login] page` |
|
|
133
|
+
| Click | `User click [Submit] button` |
|
|
134
|
+
| Fill | `User fill [Email] field with {{email}}` |
|
|
135
|
+
| Assert visible | `User see [Welcome] heading is visible` |
|
|
136
|
+
| Assert text | `User see [Title] heading with {{title}}` |
|
|
137
|
+
| Assert state | `User see [Submit] button is disabled` |
|
|
138
|
+
| Wait for | `User wait for [Modal] dialog is visible` |
|
|
139
|
+
| Table row | `User see [Users] table has row with {{name}}` |
|
|
140
|
+
| Table column | `User see [Users] table has [Email] column` |
|
|
141
|
+
|
|
142
|
+
States: `hidden` `visible` `disabled` `enabled` `checked` `unchecked` `focused` `empty` `loading` `selected`
|
|
171
143
|
|
|
172
144
|
### Tags
|
|
173
145
|
|
|
174
146
|
| Tag | Purpose |
|
|
175
147
|
|---|---|
|
|
176
148
|
| `@auth:role` | Use Playwright storage state for auth |
|
|
149
|
+
| `@no-auth` | Disable inherited auth for this scenario |
|
|
177
150
|
| `@steps:name` | Define reusable step group |
|
|
178
151
|
| `@extend:name` | Inherit steps from another scenario |
|
|
179
152
|
| `@manual` | Skip scenario in generation |
|
|
180
153
|
|
|
181
|
-
### Selector
|
|
154
|
+
### YAML Selector Keys
|
|
155
|
+
|
|
156
|
+
Keys are **lowercase with spaces**, Unicode preserved:
|
|
157
|
+
- `[Search Content]` → `search content:`
|
|
158
|
+
- `[Thời gian]` → `thời gian:`
|
|
159
|
+
- Same label, different types → `add campaign--button:` / `add campaign--text:`
|
|
160
|
+
|
|
161
|
+
### Selector Types
|
|
182
162
|
|
|
183
163
|
```yaml
|
|
184
|
-
|
|
185
|
-
type: '
|
|
186
|
-
value: '
|
|
187
|
-
name: '
|
|
188
|
-
nth: 0
|
|
189
|
-
exact: true # exact name match (avoid "Submit" matching "Submit Form")
|
|
190
|
-
scope: 'desktop navigation' # scope to parent landmark
|
|
191
|
-
match: 'exact' # exact text match for getByText
|
|
164
|
+
search:
|
|
165
|
+
type: 'placeholder' # testid, role, placeholder, label, text, locator, page
|
|
166
|
+
value: 'Search users' # placeholder text, role name, CSS selector, etc.
|
|
167
|
+
name: 'Search' # accessible name (for role type)
|
|
168
|
+
nth: 0 # element index (for multiple matches)
|
|
192
169
|
```
|
|
193
170
|
|
|
194
|
-
|
|
171
|
+
Priority: `data-testid` > `role+name` > `placeholder` > `label` > `text` > `CSS locator`
|
|
195
172
|
|
|
196
173
|
---
|
|
197
174
|
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { expect } from '@playwright/test';
|
|
2
|
+
declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
|
3
|
+
export { test, expect };
|
|
4
|
+
//# sourceMappingURL=specs-base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"specs-base.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAQxD,QAAA,MAAM,IAAI,6OA8DR,CAAC;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.expect = exports.test = void 0;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return test_1.expect; } });
|
|
6
|
+
// Share one context per storageState — avoids creating multiple sessions
|
|
7
|
+
// that trigger server rate limiting or session invalidation
|
|
8
|
+
const contextCache = new Map();
|
|
9
|
+
const GOTO_PATCHED = Symbol('goto-patched');
|
|
10
|
+
const test = test_1.test.extend({
|
|
11
|
+
page: async ({ browser, storageState }, use) => {
|
|
12
|
+
if (storageState) {
|
|
13
|
+
const cacheKey = typeof storageState === 'string' ? storageState : JSON.stringify(storageState);
|
|
14
|
+
let cached = contextCache.get(cacheKey);
|
|
15
|
+
if (!cached) {
|
|
16
|
+
const context = await browser.newContext({ storageState });
|
|
17
|
+
const page = await context.newPage();
|
|
18
|
+
cached = { context, page };
|
|
19
|
+
contextCache.set(cacheKey, cached);
|
|
20
|
+
}
|
|
21
|
+
const page = cached.page;
|
|
22
|
+
// Patch goto once: skip navigation if already on the target path
|
|
23
|
+
if (!page[GOTO_PATCHED]) {
|
|
24
|
+
const originalGoto = page.goto.bind(page);
|
|
25
|
+
page.goto = async function (url, options) {
|
|
26
|
+
try {
|
|
27
|
+
const currentPath = new URL(page.url()).pathname;
|
|
28
|
+
if (currentPath === url || currentPath === url + '/') {
|
|
29
|
+
// Dismiss any open overlays (dropdowns, dialogs) from previous test
|
|
30
|
+
await page.keyboard.press('Escape').catch(() => { });
|
|
31
|
+
await page.locator('body').click({ position: { x: 1, y: 1 }, force: true }).catch(() => { });
|
|
32
|
+
// Safety check: if a fixed-position overlay (modal/dialog) is still present, full reload
|
|
33
|
+
// eslint-disable-next-line no-eval -- runs in browser context via Playwright
|
|
34
|
+
const hasOverlay = await page.evaluate(`(() => {
|
|
35
|
+
const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
|
|
36
|
+
if (!el) return false;
|
|
37
|
+
let current = el;
|
|
38
|
+
while (current && current !== document.body) {
|
|
39
|
+
if (getComputedStyle(current).position === 'fixed') return true;
|
|
40
|
+
current = current.parentElement;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
})()`).catch(() => true);
|
|
44
|
+
if (hasOverlay) {
|
|
45
|
+
return originalGoto(url, options);
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// page.url() might be about:blank on first run
|
|
52
|
+
}
|
|
53
|
+
return originalGoto(url, options);
|
|
54
|
+
};
|
|
55
|
+
page[GOTO_PATCHED] = true;
|
|
56
|
+
}
|
|
57
|
+
await use(page);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// No storageState: fresh context (e.g., unauthenticated tests)
|
|
61
|
+
const context = await browser.newContext();
|
|
62
|
+
const page = await context.newPage();
|
|
63
|
+
await use(page);
|
|
64
|
+
await page.close();
|
|
65
|
+
await context.close();
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
exports.test = test;
|
|
70
|
+
//# sourceMappingURL=specs-base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"specs-base.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-base.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { test as base, expect } from '@playwright/test';
|
|
2
|
+
import type { BrowserContext, Page } from '@playwright/test';
|
|
3
|
+
|
|
4
|
+
// Share one context per storageState — avoids creating multiple sessions
|
|
5
|
+
// that trigger server rate limiting or session invalidation
|
|
6
|
+
const contextCache = new Map<string, { context: BrowserContext; page: Page }>();
|
|
7
|
+
const GOTO_PATCHED = Symbol('goto-patched');
|
|
8
|
+
|
|
9
|
+
const test = base.extend({
|
|
10
|
+
page: async ({ browser, storageState }, use) => {
|
|
11
|
+
if (storageState) {
|
|
12
|
+
const cacheKey = typeof storageState === 'string' ? storageState : JSON.stringify(storageState);
|
|
13
|
+
|
|
14
|
+
let cached = contextCache.get(cacheKey);
|
|
15
|
+
if (!cached) {
|
|
16
|
+
const context = await browser.newContext({ storageState });
|
|
17
|
+
const page = await context.newPage();
|
|
18
|
+
cached = { context, page };
|
|
19
|
+
contextCache.set(cacheKey, cached);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const page = cached.page;
|
|
23
|
+
|
|
24
|
+
// Patch goto once: skip navigation if already on the target path
|
|
25
|
+
if (!(page as any)[GOTO_PATCHED]) {
|
|
26
|
+
const originalGoto = page.goto.bind(page);
|
|
27
|
+
page.goto = async function (url: string, options?: any) {
|
|
28
|
+
try {
|
|
29
|
+
const currentPath = new URL(page.url()).pathname;
|
|
30
|
+
if (currentPath === url || currentPath === url + '/') {
|
|
31
|
+
// Dismiss any open overlays (dropdowns, dialogs) from previous test
|
|
32
|
+
await page.keyboard.press('Escape').catch(() => {});
|
|
33
|
+
await page.locator('body').click({ position: { x: 1, y: 1 }, force: true }).catch(() => {});
|
|
34
|
+
|
|
35
|
+
// Safety check: if a fixed-position overlay (modal/dialog) is still present, full reload
|
|
36
|
+
// eslint-disable-next-line no-eval -- runs in browser context via Playwright
|
|
37
|
+
const hasOverlay = await page.evaluate(`(() => {
|
|
38
|
+
const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
|
|
39
|
+
if (!el) return false;
|
|
40
|
+
let current = el;
|
|
41
|
+
while (current && current !== document.body) {
|
|
42
|
+
if (getComputedStyle(current).position === 'fixed') return true;
|
|
43
|
+
current = current.parentElement;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
})()`).catch(() => true);
|
|
47
|
+
|
|
48
|
+
if (hasOverlay) {
|
|
49
|
+
return originalGoto(url, options);
|
|
50
|
+
}
|
|
51
|
+
return null as any;
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// page.url() might be about:blank on first run
|
|
55
|
+
}
|
|
56
|
+
return originalGoto(url, options);
|
|
57
|
+
};
|
|
58
|
+
(page as any)[GOTO_PATCHED] = true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await use(page);
|
|
62
|
+
} else {
|
|
63
|
+
// No storageState: fresh context (e.g., unauthenticated tests)
|
|
64
|
+
const context = await browser.newContext();
|
|
65
|
+
const page = await context.newPage();
|
|
66
|
+
await use(page);
|
|
67
|
+
await page.close();
|
|
68
|
+
await context.close();
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export { test, expect };
|
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
await {{> locator}}.fill('{{fillValue}}');
|
|
1
|
+
await {{> locator}}.fill('{{escapeQuotes fillValue}}');
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
await page.waitForLoadState('networkidle');
|
|
2
1
|
const {{columnIndexVar}} = (await page.getByRole('columnheader').allTextContents()).findIndex(h => h.includes('{{columnName}}'));
|
|
3
2
|
await expect(page.getByRole('row').nth({{rowNth}}).getByRole('cell').nth({{columnIndexVar}})).toHaveText('{{dataValue}}');
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
await page.waitForLoadState('networkidle');
|
|
2
1
|
{{#if name}}
|
|
3
2
|
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
3
|
{{else}}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
await page.waitForLoadState('networkidle');
|
|
2
1
|
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,4 +1,3 @@
|
|
|
1
|
-
await page.waitForLoadState('networkidle');
|
|
2
1
|
{{#if name}}
|
|
3
2
|
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
3
|
{{else}}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
await page.waitForLoadState('networkidle');
|
|
2
1
|
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,4 +1,3 @@
|
|
|
1
|
-
await page.waitForLoadState('networkidle');
|
|
2
1
|
{{#if name}}
|
|
3
2
|
{{#if dataValue}}
|
|
4
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}}).toBeVisible();
|
package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
await page.goto('{{#if baseURL}}{{baseURL}}{{/if}}{{path}}', { waitUntil: '
|
|
1
|
+
await page.goto('{{#if baseURL}}{{baseURL}}{{/if}}{{path}}', { waitUntil: 'load' });
|