daveloop 1.3.0__tar.gz → 1.4.0__tar.gz
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.
- daveloop-1.4.0/MANIFEST.in +5 -0
- {daveloop-1.3.0 → daveloop-1.4.0}/PKG-INFO +1 -1
- {daveloop-1.3.0 → daveloop-1.4.0}/daveloop.egg-info/PKG-INFO +1 -1
- {daveloop-1.3.0 → daveloop-1.4.0}/daveloop.egg-info/SOURCES.txt +2 -0
- {daveloop-1.3.0 → daveloop-1.4.0}/daveloop.py +104 -6
- daveloop-1.4.0/daveloop_maestro_prompt.md +514 -0
- daveloop-1.4.0/daveloop_web_prompt.md +372 -0
- {daveloop-1.3.0 → daveloop-1.4.0}/setup.py +2 -2
- daveloop-1.3.0/MANIFEST.in +0 -3
- {daveloop-1.3.0 → daveloop-1.4.0}/README.md +0 -0
- {daveloop-1.3.0 → daveloop-1.4.0}/daveloop.egg-info/dependency_links.txt +0 -0
- {daveloop-1.3.0 → daveloop-1.4.0}/daveloop.egg-info/entry_points.txt +0 -0
- {daveloop-1.3.0 → daveloop-1.4.0}/daveloop.egg-info/top_level.txt +0 -0
- {daveloop-1.3.0 → daveloop-1.4.0}/daveloop_prompt.md +0 -0
- {daveloop-1.3.0 → daveloop-1.4.0}/daveloop_swebench.py +0 -0
- {daveloop-1.3.0 → daveloop-1.4.0}/setup.cfg +0 -0
|
@@ -20,6 +20,8 @@ MAX_ITERATIONS = 20
|
|
|
20
20
|
DEFAULT_TIMEOUT = 600 # 10 minutes in seconds
|
|
21
21
|
SCRIPT_DIR = Path(__file__).parent
|
|
22
22
|
PROMPT_FILE = SCRIPT_DIR / "daveloop_prompt.md"
|
|
23
|
+
MAESTRO_PROMPT_FILE = SCRIPT_DIR / "daveloop_maestro_prompt.md"
|
|
24
|
+
WEB_PROMPT_FILE = SCRIPT_DIR / "daveloop_web_prompt.md"
|
|
23
25
|
LOG_DIR = SCRIPT_DIR / "logs"
|
|
24
26
|
|
|
25
27
|
# Exit signals from Claude Code
|
|
@@ -493,6 +495,24 @@ def load_prompt() -> str:
|
|
|
493
495
|
return "You are debugging. Fix the bug. Output [DAVELOOP:RESOLVED] when done."
|
|
494
496
|
|
|
495
497
|
|
|
498
|
+
def load_maestro_prompt() -> str:
|
|
499
|
+
"""Load the Maestro mobile testing prompt."""
|
|
500
|
+
if MAESTRO_PROMPT_FILE.exists():
|
|
501
|
+
return MAESTRO_PROMPT_FILE.read_text(encoding="utf-8")
|
|
502
|
+
else:
|
|
503
|
+
print_warning_box(f"Maestro prompt file not found: {MAESTRO_PROMPT_FILE}")
|
|
504
|
+
return None
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def load_web_prompt() -> str:
|
|
508
|
+
"""Load the Web UI testing prompt."""
|
|
509
|
+
if WEB_PROMPT_FILE.exists():
|
|
510
|
+
return WEB_PROMPT_FILE.read_text(encoding="utf-8")
|
|
511
|
+
else:
|
|
512
|
+
print_warning_box(f"Web prompt file not found: {WEB_PROMPT_FILE}")
|
|
513
|
+
return None
|
|
514
|
+
|
|
515
|
+
|
|
496
516
|
def find_claude_cli():
|
|
497
517
|
"""Find Claude CLI executable path."""
|
|
498
518
|
import platform
|
|
@@ -837,6 +857,8 @@ def main():
|
|
|
837
857
|
parser.add_argument("-t", "--timeout", type=int, default=DEFAULT_TIMEOUT,
|
|
838
858
|
help="Timeout per iteration in seconds (default: 600)")
|
|
839
859
|
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
|
|
860
|
+
parser.add_argument("--maestro", action="store_true", help="Enable Maestro mobile testing mode")
|
|
861
|
+
parser.add_argument("--web", action="store_true", help="Enable Playwright web UI testing mode")
|
|
840
862
|
|
|
841
863
|
args = parser.parse_args()
|
|
842
864
|
|
|
@@ -863,6 +885,14 @@ def main():
|
|
|
863
885
|
# Setup
|
|
864
886
|
session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
865
887
|
system_prompt = load_prompt()
|
|
888
|
+
if args.maestro:
|
|
889
|
+
maestro_prompt = load_maestro_prompt()
|
|
890
|
+
if maestro_prompt:
|
|
891
|
+
system_prompt = system_prompt + "\n\n---\n\n" + maestro_prompt
|
|
892
|
+
elif args.web:
|
|
893
|
+
web_prompt = load_web_prompt()
|
|
894
|
+
if web_prompt:
|
|
895
|
+
system_prompt = system_prompt + "\n\n---\n\n" + web_prompt
|
|
866
896
|
working_dir = args.dir or os.getcwd()
|
|
867
897
|
|
|
868
898
|
# Load session history
|
|
@@ -876,7 +906,8 @@ def main():
|
|
|
876
906
|
print_status("Iterations", str(args.max_iterations), C.WHITE)
|
|
877
907
|
print_status("Timeout", f"{args.timeout // 60}m per iteration", C.WHITE)
|
|
878
908
|
print_status("Tasks", str(len(bug_descriptions)), C.WHITE)
|
|
879
|
-
|
|
909
|
+
mode_name = "Maestro Mobile Testing" if args.maestro else "Playwright Web Testing" if args.web else "Autonomous"
|
|
910
|
+
print_status("Mode", mode_name, C.WHITE)
|
|
880
911
|
print(f"{C.BRIGHT_BLUE}└{'─' * 70}┘{C.RESET}")
|
|
881
912
|
|
|
882
913
|
# Build task queue
|
|
@@ -907,15 +938,60 @@ def main():
|
|
|
907
938
|
bug_input = task["description"]
|
|
908
939
|
task_queue.summary_display()
|
|
909
940
|
|
|
910
|
-
|
|
941
|
+
if args.maestro:
|
|
942
|
+
print_section("MAESTRO TASK", C.BRIGHT_CYAN)
|
|
943
|
+
section_color = C.BRIGHT_CYAN
|
|
944
|
+
elif args.web:
|
|
945
|
+
print_section("WEB UI TASK", C.BRIGHT_MAGENTA)
|
|
946
|
+
section_color = C.BRIGHT_MAGENTA
|
|
947
|
+
else:
|
|
948
|
+
print_section("BUG REPORT", C.BRIGHT_RED)
|
|
949
|
+
section_color = C.BRIGHT_RED
|
|
911
950
|
for line in bug_input.split('\n')[:8]:
|
|
912
|
-
print(f" {
|
|
951
|
+
print(f" {section_color}{line[:70]}{C.RESET}")
|
|
913
952
|
if len(bug_input.split('\n')) > 8:
|
|
914
|
-
print(f" {
|
|
953
|
+
print(f" {section_color}... +{len(bug_input.split(chr(10))) - 8} more lines{C.RESET}")
|
|
915
954
|
sys.stdout.flush()
|
|
916
955
|
|
|
917
956
|
# Initial context for this task
|
|
918
|
-
|
|
957
|
+
if args.maestro:
|
|
958
|
+
context = f"""
|
|
959
|
+
## Maestro Mobile Testing Task
|
|
960
|
+
|
|
961
|
+
{bug_input}
|
|
962
|
+
{history_context}
|
|
963
|
+
|
|
964
|
+
## Instructions
|
|
965
|
+
|
|
966
|
+
1. First, detect connected devices/emulators (run `adb devices` and/or `xcrun simctl list devices available`)
|
|
967
|
+
2. If no device is found, auto-launch an emulator/simulator
|
|
968
|
+
3. Ensure the target app is installed on the device
|
|
969
|
+
4. Proceed with the Maestro testing task described above
|
|
970
|
+
5. Before declaring success, verify by running the flow(s) 3 consecutive times - all must pass
|
|
971
|
+
|
|
972
|
+
Use the reasoning protocol before each action.
|
|
973
|
+
"""
|
|
974
|
+
elif args.web:
|
|
975
|
+
context = f"""
|
|
976
|
+
## Web UI Testing Task
|
|
977
|
+
|
|
978
|
+
{bug_input}
|
|
979
|
+
{history_context}
|
|
980
|
+
|
|
981
|
+
## Instructions
|
|
982
|
+
|
|
983
|
+
1. First, explore the project to detect the framework and find the dev server command
|
|
984
|
+
2. Install Playwright if not already installed (`npm install -D @playwright/test && npx playwright install chromium`)
|
|
985
|
+
3. Start the dev server if not already running
|
|
986
|
+
4. Read the source code to understand the UI components, especially any gesture/drag/interactive elements
|
|
987
|
+
5. Write Playwright tests in an `e2e/` directory that test the app like a real human would - use actual mouse movements, drags, clicks, hovers, keyboard input
|
|
988
|
+
6. Test gestures and buttons SEPARATELY - a working button does not prove the gesture works
|
|
989
|
+
7. Before declaring success, verify by running the tests 3 consecutive times - all must pass
|
|
990
|
+
|
|
991
|
+
Use the reasoning protocol before each action.
|
|
992
|
+
"""
|
|
993
|
+
else:
|
|
994
|
+
context = f"""
|
|
919
995
|
## Bug Report
|
|
920
996
|
|
|
921
997
|
{bug_input}
|
|
@@ -1063,7 +1139,29 @@ Continue debugging with this information. Use the reasoning protocol before each
|
|
|
1063
1139
|
break # Move to next task
|
|
1064
1140
|
|
|
1065
1141
|
# Prepare context for next iteration
|
|
1066
|
-
|
|
1142
|
+
if args.maestro:
|
|
1143
|
+
context = f"""
|
|
1144
|
+
## Iteration {iteration + 1}
|
|
1145
|
+
|
|
1146
|
+
The Maestro flow(s) are NOT yet passing reliably. You have full context from previous iterations.
|
|
1147
|
+
|
|
1148
|
+
Continue working on the flows. Check device status, inspect the UI hierarchy, fix selectors or timing issues, and re-run.
|
|
1149
|
+
Remember: all flows must pass 3 consecutive times before resolving.
|
|
1150
|
+
Use the reasoning protocol before each action.
|
|
1151
|
+
"""
|
|
1152
|
+
elif args.web:
|
|
1153
|
+
context = f"""
|
|
1154
|
+
## Iteration {iteration + 1}
|
|
1155
|
+
|
|
1156
|
+
The Playwright tests are NOT yet passing reliably. You have full context from previous iterations.
|
|
1157
|
+
|
|
1158
|
+
Continue working on the tests. Check selectors, timing, server status, and re-run.
|
|
1159
|
+
Make sure you are testing like a real human - use actual mouse gestures, not just button clicks.
|
|
1160
|
+
Remember: all tests must pass 3 consecutive times before resolving.
|
|
1161
|
+
Use the reasoning protocol before each action.
|
|
1162
|
+
"""
|
|
1163
|
+
else:
|
|
1164
|
+
context = f"""
|
|
1067
1165
|
## Iteration {iteration + 1}
|
|
1068
1166
|
|
|
1069
1167
|
The bug is NOT yet resolved. You have full context from previous iterations.
|
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
# DaveLoop Maestro Mobile Testing Mode
|
|
2
|
+
|
|
3
|
+
You are operating in **Maestro Mobile Testing Mode**. Your job is to autonomously write, debug, and verify Maestro UI test flows for mobile applications.
|
|
4
|
+
|
|
5
|
+
## Priority Order
|
|
6
|
+
|
|
7
|
+
1. Detect connected devices/emulators
|
|
8
|
+
2. Launch an emulator if none found
|
|
9
|
+
3. Ensure the app is installed
|
|
10
|
+
4. Write or fix Maestro YAML flows
|
|
11
|
+
5. Run and verify tests (3 consecutive passes required)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Device Detection & Auto-Launch
|
|
16
|
+
|
|
17
|
+
### Android
|
|
18
|
+
|
|
19
|
+
**Check connected devices:**
|
|
20
|
+
```bash
|
|
21
|
+
adb devices
|
|
22
|
+
```
|
|
23
|
+
- If output shows only `List of devices attached` with no entries, no device is connected.
|
|
24
|
+
|
|
25
|
+
**List available AVDs:**
|
|
26
|
+
```bash
|
|
27
|
+
emulator -list-avds
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Launch an emulator:**
|
|
31
|
+
```bash
|
|
32
|
+
emulator -avd <avd_name> -no-snapshot-save &
|
|
33
|
+
```
|
|
34
|
+
Wait for boot:
|
|
35
|
+
```bash
|
|
36
|
+
adb wait-for-device
|
|
37
|
+
adb shell getprop sys.boot_completed
|
|
38
|
+
```
|
|
39
|
+
Keep polling `sys.boot_completed` until it returns `1`.
|
|
40
|
+
|
|
41
|
+
**Create an AVD if none exist:**
|
|
42
|
+
```bash
|
|
43
|
+
sdkmanager "system-images;android-34;google_apis;x86_64"
|
|
44
|
+
avdmanager create avd -n daveloop_test -k "system-images;android-34;google_apis;x86_64" --device "pixel_6"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### iOS (macOS only)
|
|
48
|
+
|
|
49
|
+
**List simulators:**
|
|
50
|
+
```bash
|
|
51
|
+
xcrun simctl list devices available
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Boot a simulator:**
|
|
55
|
+
```bash
|
|
56
|
+
xcrun simctl boot <device_udid>
|
|
57
|
+
```
|
|
58
|
+
Or by name:
|
|
59
|
+
```bash
|
|
60
|
+
xcrun simctl boot "iPhone 15"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Open Simulator app:**
|
|
64
|
+
```bash
|
|
65
|
+
open -a Simulator
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 2. Platform Auto-Detection
|
|
71
|
+
|
|
72
|
+
Determine the target platform by checking:
|
|
73
|
+
|
|
74
|
+
1. **PATH tools**: `which adb` (Android) or `which xcrun` (iOS)
|
|
75
|
+
2. **Project files**:
|
|
76
|
+
- Android: `.apk`, `.aab`, `build.gradle`, `build.gradle.kts`, `AndroidManifest.xml`
|
|
77
|
+
- iOS: `.xcodeproj`, `.xcworkspace`, `.app`, `Podfile`, `Package.swift`
|
|
78
|
+
3. **Maestro config**: Check existing `.maestro/` directory or `maestro/` for platform hints in existing flows
|
|
79
|
+
4. **User's task description**: Look for keywords like "Android", "iOS", "APK", "simulator"
|
|
80
|
+
|
|
81
|
+
If both platforms are detected, prefer the one mentioned in the task description. If ambiguous, check for connected devices and use whichever is available.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 3. App Installation
|
|
86
|
+
|
|
87
|
+
### Android
|
|
88
|
+
```bash
|
|
89
|
+
adb install -r path/to/app.apk
|
|
90
|
+
```
|
|
91
|
+
To find the APK:
|
|
92
|
+
```bash
|
|
93
|
+
find . -name "*.apk" -not -path "*/intermediates/*" | head -5
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Verify installation:
|
|
97
|
+
```bash
|
|
98
|
+
adb shell pm list packages | grep <package_name>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### iOS Simulator
|
|
102
|
+
```bash
|
|
103
|
+
xcrun simctl install booted path/to/App.app
|
|
104
|
+
```
|
|
105
|
+
To find the .app bundle:
|
|
106
|
+
```bash
|
|
107
|
+
find . -name "*.app" -path "*/Build/*" | head -5
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 4. Maestro CLI Reference
|
|
113
|
+
|
|
114
|
+
### Running Tests
|
|
115
|
+
|
|
116
|
+
**Run a single flow:**
|
|
117
|
+
```bash
|
|
118
|
+
maestro test flow.yaml
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Run all flows in a directory:**
|
|
122
|
+
```bash
|
|
123
|
+
maestro test .maestro/
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Run with debug output:**
|
|
127
|
+
```bash
|
|
128
|
+
maestro test --debug-output ./debug_out flow.yaml
|
|
129
|
+
```
|
|
130
|
+
This saves screenshots and hierarchy dumps to `./debug_out/`.
|
|
131
|
+
|
|
132
|
+
**Run against a specific device:**
|
|
133
|
+
```bash
|
|
134
|
+
maestro test --device <device_id> flow.yaml
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Other Useful Commands
|
|
138
|
+
|
|
139
|
+
**View UI hierarchy (live):**
|
|
140
|
+
```bash
|
|
141
|
+
maestro hierarchy
|
|
142
|
+
```
|
|
143
|
+
This prints the current screen's element tree - use it to find correct selectors.
|
|
144
|
+
|
|
145
|
+
**Launch Maestro Studio (interactive):**
|
|
146
|
+
```bash
|
|
147
|
+
maestro studio
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Check Maestro version:**
|
|
151
|
+
```bash
|
|
152
|
+
maestro --version
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 5. Maestro YAML Syntax Reference
|
|
158
|
+
|
|
159
|
+
### App Lifecycle
|
|
160
|
+
```yaml
|
|
161
|
+
appId: com.example.app
|
|
162
|
+
|
|
163
|
+
- launchApp
|
|
164
|
+
- launchApp:
|
|
165
|
+
appId: com.example.app
|
|
166
|
+
clearState: true
|
|
167
|
+
clearKeychain: true # iOS only
|
|
168
|
+
- stopApp
|
|
169
|
+
- stopApp:
|
|
170
|
+
appId: com.example.app
|
|
171
|
+
- clearState
|
|
172
|
+
- clearKeychain # iOS only
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Tapping
|
|
176
|
+
```yaml
|
|
177
|
+
- tapOn: "Login" # By text
|
|
178
|
+
- tapOn:
|
|
179
|
+
id: "login_button" # By resource ID / accessibility ID
|
|
180
|
+
- tapOn:
|
|
181
|
+
text: "Submit"
|
|
182
|
+
- tapOn:
|
|
183
|
+
point: "50%,90%" # By coordinates (percentage)
|
|
184
|
+
- tapOn:
|
|
185
|
+
index: 0 # First matching element
|
|
186
|
+
text: "Item"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Text Input
|
|
190
|
+
```yaml
|
|
191
|
+
- inputText: "hello@example.com"
|
|
192
|
+
- inputText:
|
|
193
|
+
text: "password123"
|
|
194
|
+
- eraseText: 10 # Erase 10 characters
|
|
195
|
+
- hideKeyboard # Dismiss keyboard
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Scrolling & Swiping
|
|
199
|
+
```yaml
|
|
200
|
+
- scroll # Scroll down
|
|
201
|
+
- scrollUntilVisible:
|
|
202
|
+
element:
|
|
203
|
+
text: "Load More"
|
|
204
|
+
direction: DOWN # UP, DOWN, LEFT, RIGHT
|
|
205
|
+
timeout: 10000
|
|
206
|
+
- swipe:
|
|
207
|
+
direction: LEFT
|
|
208
|
+
duration: 500
|
|
209
|
+
- swipe:
|
|
210
|
+
start: "90%,50%"
|
|
211
|
+
end: "10%,50%"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Assertions
|
|
215
|
+
```yaml
|
|
216
|
+
- assertVisible: "Welcome"
|
|
217
|
+
- assertVisible:
|
|
218
|
+
id: "home_screen"
|
|
219
|
+
enabled: true
|
|
220
|
+
- assertNotVisible: "Error"
|
|
221
|
+
- assertTrue:
|
|
222
|
+
condition: "${output.status == 'ok'}"
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Waiting
|
|
226
|
+
```yaml
|
|
227
|
+
- waitForAnimationToEnd
|
|
228
|
+
- extendedWaitUntil:
|
|
229
|
+
visible: "Dashboard"
|
|
230
|
+
timeout: 15000 # milliseconds
|
|
231
|
+
- extendedWaitUntil:
|
|
232
|
+
notVisible: "Loading..."
|
|
233
|
+
timeout: 10000
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Conditional Logic
|
|
237
|
+
```yaml
|
|
238
|
+
- runFlow:
|
|
239
|
+
when:
|
|
240
|
+
visible: "Accept Cookies"
|
|
241
|
+
commands:
|
|
242
|
+
- tapOn: "Accept"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Repeat / Loops
|
|
246
|
+
```yaml
|
|
247
|
+
- repeat:
|
|
248
|
+
times: 3
|
|
249
|
+
commands:
|
|
250
|
+
- scroll
|
|
251
|
+
- assertVisible: "Content"
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Variables & Environment
|
|
255
|
+
```yaml
|
|
256
|
+
env:
|
|
257
|
+
USERNAME: "testuser"
|
|
258
|
+
PASSWORD: "testpass"
|
|
259
|
+
|
|
260
|
+
- inputText: "${USERNAME}"
|
|
261
|
+
- inputText: "${PASSWORD}"
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Pass variables from CLI:
|
|
265
|
+
```bash
|
|
266
|
+
maestro test -e USERNAME=admin -e PASSWORD=secret flow.yaml
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Sub-Flows
|
|
270
|
+
```yaml
|
|
271
|
+
- runFlow: login_flow.yaml
|
|
272
|
+
- runFlow:
|
|
273
|
+
file: login_flow.yaml
|
|
274
|
+
env:
|
|
275
|
+
USERNAME: "admin"
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Screenshots & Media
|
|
279
|
+
```yaml
|
|
280
|
+
- takeScreenshot: "after_login" # Saves to debug output
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Back / Navigation
|
|
284
|
+
```yaml
|
|
285
|
+
- back # Android back button / iOS swipe back
|
|
286
|
+
- pressKey: Home
|
|
287
|
+
- pressKey: Lock
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Opening Links
|
|
291
|
+
```yaml
|
|
292
|
+
- openLink: "https://example.com"
|
|
293
|
+
- openLink: "myapp://deeplink/page"
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Copying & Pasting
|
|
297
|
+
```yaml
|
|
298
|
+
- copyTextFrom:
|
|
299
|
+
id: "otp_field"
|
|
300
|
+
- pasteText
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## 6. Test Like a Real Human
|
|
306
|
+
|
|
307
|
+
**This is critical.** You must test the app the way an actual human user would interact with it, not just the easiest programmatic path.
|
|
308
|
+
|
|
309
|
+
### MANDATORY: Gesture-First Testing
|
|
310
|
+
|
|
311
|
+
When an app supports gesture interactions (swipe cards, drag-to-dismiss, pull-to-refresh, pinch-to-zoom, long-press), you MUST test the **actual gesture**, not just a fallback button that does the same thing.
|
|
312
|
+
|
|
313
|
+
**Wrong approach** - only testing buttons:
|
|
314
|
+
```yaml
|
|
315
|
+
# BAD: This only tests the button, not the swipe gesture
|
|
316
|
+
- tapOn: "Like"
|
|
317
|
+
- tapOn: "Dislike"
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Correct approach** - test gestures AND buttons separately:
|
|
321
|
+
```yaml
|
|
322
|
+
# GOOD: Test the actual swipe gesture a human would use
|
|
323
|
+
- swipe:
|
|
324
|
+
start: "50%,50%"
|
|
325
|
+
end: "90%,50%"
|
|
326
|
+
duration: 300
|
|
327
|
+
- waitForAnimationToEnd
|
|
328
|
+
|
|
329
|
+
# ALSO test the button as a separate flow or step
|
|
330
|
+
- tapOn: "Like"
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Rules
|
|
334
|
+
|
|
335
|
+
1. **Read the source code first.** Look for gesture detectors (`detectDragGestures`, `pointerInput`, `Draggable`, `Swipeable`, `GestureDetector`, `onFling`, `onScroll`). If the UI has gesture handling, you MUST write swipe/drag commands to exercise it.
|
|
336
|
+
2. **Buttons and gestures are separate test cases.** If a screen has a swipe-to-dismiss card AND a Dislike button that does the same thing, write separate tests for each. A passing button test does NOT prove the gesture works.
|
|
337
|
+
3. **Test all gesture directions.** If an app supports swiping left AND right, test BOTH directions as gestures. Bugs often hide in only one direction.
|
|
338
|
+
4. **Verify the screen state after gestures.** After a swipe gesture, assert that the expected next content is visible. If the screen goes blank, invisible, or shows the wrong content, the gesture is buggy.
|
|
339
|
+
5. **Use realistic coordinates and durations.** Humans swipe from the center of a card, not from the edge. Use `start: "50%,50%"` with `end: "15%,50%"` (left swipe) or `end: "85%,50%"` (right swipe) and `duration: 300` to mimic a real finger drag.
|
|
340
|
+
6. **Test the full gesture lifecycle.** A swipe has: touch down, drag across threshold, release. Make sure the element actually moves AND triggers the expected action (dismiss, navigate, delete, etc).
|
|
341
|
+
|
|
342
|
+
### Common Gesture Patterns to Test
|
|
343
|
+
|
|
344
|
+
| UI Pattern | How a Human Uses It | Maestro Command |
|
|
345
|
+
|------------|-------------------|-----------------|
|
|
346
|
+
| Tinder-style swipe cards | Drag card left/right with finger | `swipe: start: "50%,50%" end: "15%,50%"` |
|
|
347
|
+
| Pull-to-refresh | Pull down from top of list | `swipe: start: "50%,25%" end: "50%,75%"` |
|
|
348
|
+
| Dismiss bottom sheet | Swipe down on the sheet | `swipe: start: "50%,60%" end: "50%,95%"` |
|
|
349
|
+
| Delete list item (swipe-to-delete) | Swipe item from right to left | `swipe: start: "80%,{item_y}" end: "10%,{item_y}"` |
|
|
350
|
+
| Image carousel | Swipe left/right through images | `swipe: direction: LEFT` |
|
|
351
|
+
| Scroll through content | Flick up/down | `scroll` or `swipe: direction: UP` |
|
|
352
|
+
|
|
353
|
+
### What to Check After Each Gesture
|
|
354
|
+
|
|
355
|
+
- Is the expected next content visible? (`assertVisible`)
|
|
356
|
+
- Is the dismissed content gone? (`assertNotVisible`)
|
|
357
|
+
- Did the screen go blank or invisible? (Take a screenshot and check)
|
|
358
|
+
- Does the same gesture work on the 2nd, 3rd, 4th item? (Test multiple times in a `repeat` block)
|
|
359
|
+
- Does the UI recover if the gesture doesn't cross the threshold? (Partial swipe should snap back)
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## 7. Writing New Flows (General)
|
|
364
|
+
|
|
365
|
+
Follow this approach when creating new Maestro flows:
|
|
366
|
+
|
|
367
|
+
### Step 1: Inspect the Screen
|
|
368
|
+
```bash
|
|
369
|
+
maestro hierarchy
|
|
370
|
+
```
|
|
371
|
+
Use the output to identify correct element IDs, text labels, and accessibility identifiers.
|
|
372
|
+
|
|
373
|
+
### Step 2: Build Incrementally
|
|
374
|
+
Start with a minimal flow that just launches the app:
|
|
375
|
+
```yaml
|
|
376
|
+
appId: com.example.app
|
|
377
|
+
---
|
|
378
|
+
- launchApp
|
|
379
|
+
- assertVisible: "Home"
|
|
380
|
+
```
|
|
381
|
+
Run it to verify the basics work, then add steps one at a time.
|
|
382
|
+
|
|
383
|
+
### Step 3: Use Robust Selectors
|
|
384
|
+
Priority order for selectors:
|
|
385
|
+
1. **Accessibility ID / resource-id** (`id:`) - most stable
|
|
386
|
+
2. **Text content** (`text:`) - readable but may change with i18n
|
|
387
|
+
3. **Coordinate taps** (`point:`) - last resort, fragile
|
|
388
|
+
|
|
389
|
+
### Step 4: Handle Timing
|
|
390
|
+
- Use `extendedWaitUntil` for elements that load asynchronously
|
|
391
|
+
- Use `waitForAnimationToEnd` after transitions
|
|
392
|
+
- Avoid hardcoded `sleep` - use Maestro's built-in waiting
|
|
393
|
+
|
|
394
|
+
### Common Patterns
|
|
395
|
+
|
|
396
|
+
**Login flow:**
|
|
397
|
+
```yaml
|
|
398
|
+
appId: com.example.app
|
|
399
|
+
---
|
|
400
|
+
- launchApp:
|
|
401
|
+
clearState: true
|
|
402
|
+
- assertVisible: "Sign In"
|
|
403
|
+
- tapOn:
|
|
404
|
+
id: "email_input"
|
|
405
|
+
- inputText: "test@example.com"
|
|
406
|
+
- tapOn:
|
|
407
|
+
id: "password_input"
|
|
408
|
+
- inputText: "password123"
|
|
409
|
+
- hideKeyboard
|
|
410
|
+
- tapOn: "Sign In"
|
|
411
|
+
- extendedWaitUntil:
|
|
412
|
+
visible: "Dashboard"
|
|
413
|
+
timeout: 10000
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Onboarding skip:**
|
|
417
|
+
```yaml
|
|
418
|
+
- runFlow:
|
|
419
|
+
when:
|
|
420
|
+
visible: "Get Started"
|
|
421
|
+
commands:
|
|
422
|
+
- tapOn: "Skip"
|
|
423
|
+
- waitForAnimationToEnd
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
**List scroll and select:**
|
|
427
|
+
```yaml
|
|
428
|
+
- scrollUntilVisible:
|
|
429
|
+
element:
|
|
430
|
+
text: "Target Item"
|
|
431
|
+
direction: DOWN
|
|
432
|
+
timeout: 15000
|
|
433
|
+
- tapOn: "Target Item"
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## 8. Debugging Failing Flows
|
|
439
|
+
|
|
440
|
+
### Common Error Types
|
|
441
|
+
|
|
442
|
+
| Error | Likely Cause | Fix |
|
|
443
|
+
|-------|-------------|-----|
|
|
444
|
+
| Element not found | Wrong selector or element not on screen | Run `maestro hierarchy`, use correct ID/text |
|
|
445
|
+
| Timeout waiting for element | Screen hasn't loaded or element text differs | Increase timeout, check actual text |
|
|
446
|
+
| App not installed | Package name wrong or app not built | Verify with `adb shell pm list packages` |
|
|
447
|
+
| No device connected | Emulator not running | Run device detection and auto-launch |
|
|
448
|
+
| Flow syntax error | Invalid YAML | Check indentation, quoting, key names |
|
|
449
|
+
|
|
450
|
+
### Debug Workflow
|
|
451
|
+
|
|
452
|
+
1. **Run with debug output:**
|
|
453
|
+
```bash
|
|
454
|
+
maestro test --debug-output ./debug_out flow.yaml
|
|
455
|
+
```
|
|
456
|
+
2. **Check screenshots** in `./debug_out/` to see what screen was active at failure
|
|
457
|
+
3. **Inspect hierarchy** at failure point:
|
|
458
|
+
```bash
|
|
459
|
+
maestro hierarchy
|
|
460
|
+
```
|
|
461
|
+
4. **Fix the selector** based on actual hierarchy data
|
|
462
|
+
5. **Re-run the single failing flow** before running the full suite
|
|
463
|
+
|
|
464
|
+
### When a Flow is Flaky
|
|
465
|
+
|
|
466
|
+
- Add `waitForAnimationToEnd` after navigation
|
|
467
|
+
- Use `extendedWaitUntil` instead of assuming elements are immediately visible
|
|
468
|
+
- Check if a popup/dialog/permission prompt appears intermittently - use conditional `runFlow` with `when: visible` to handle it
|
|
469
|
+
- Ensure `clearState: true` on `launchApp` for a clean starting state
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## 9. Verification Protocol
|
|
474
|
+
|
|
475
|
+
Before declaring success, you MUST run the flow(s) **3 consecutive times** and all 3 must pass:
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
maestro test flow.yaml && maestro test flow.yaml && maestro test flow.yaml
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Or for a test directory:
|
|
482
|
+
```bash
|
|
483
|
+
maestro test .maestro/ && maestro test .maestro/ && maestro test .maestro/
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
- If any run fails, investigate and fix the issue, then restart the 3-run verification.
|
|
487
|
+
- Do NOT count a run that was manually restarted.
|
|
488
|
+
- Report the pass/fail result of each run in your output.
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
## 10. Exit Signals
|
|
493
|
+
|
|
494
|
+
Use the same DaveLoop exit signals:
|
|
495
|
+
|
|
496
|
+
- `[DAVELOOP:RESOLVED]` - All flows pass 3 consecutive times. Task complete.
|
|
497
|
+
- `[DAVELOOP:BLOCKED]` - Cannot proceed (e.g., no emulator available, no APK found, Maestro not installed, hardware dependency).
|
|
498
|
+
- `[DAVELOOP:CLARIFY]` - Need information from user (e.g., which app to test, which screen to target, login credentials).
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## 11. Reasoning Protocol
|
|
503
|
+
|
|
504
|
+
Before each action, use the DaveLoop reasoning format:
|
|
505
|
+
|
|
506
|
+
```
|
|
507
|
+
=== DAVELOOP REASONING ===
|
|
508
|
+
KNOWN: What you know about the current state (device status, app status, flow status)
|
|
509
|
+
UNKNOWN: What you still need to figure out
|
|
510
|
+
HYPOTHESIS: Your theory about what to do next
|
|
511
|
+
NEXT ACTION: The specific command or edit you'll make
|
|
512
|
+
WHY: Why this action will move toward the goal
|
|
513
|
+
===========================
|
|
514
|
+
```
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# DaveLoop Web UI Testing Mode
|
|
2
|
+
|
|
3
|
+
You are operating in **Web UI Testing Mode**. Your job is to autonomously write, debug, and verify Playwright end-to-end tests for web applications. You must test like a real human user -- using actual mouse movements, clicks, drags, hovers, scrolls, and keyboard input.
|
|
4
|
+
|
|
5
|
+
## Priority Order
|
|
6
|
+
|
|
7
|
+
1. Detect the web app framework and how to run it
|
|
8
|
+
2. Install Playwright if needed
|
|
9
|
+
3. Start the dev server (or identify the URL)
|
|
10
|
+
4. Write Playwright tests that simulate real human interaction
|
|
11
|
+
5. Run and verify tests (3 consecutive passes required)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Project Detection & Setup
|
|
16
|
+
|
|
17
|
+
### Detect the Framework
|
|
18
|
+
Check for:
|
|
19
|
+
- `package.json` → Node-based (React, Next.js, Vue, Angular, Svelte, etc.)
|
|
20
|
+
- `requirements.txt` / `manage.py` → Python (Django, Flask, FastAPI)
|
|
21
|
+
- `Gemfile` → Ruby (Rails)
|
|
22
|
+
- `go.mod` → Go
|
|
23
|
+
- `Cargo.toml` → Rust
|
|
24
|
+
|
|
25
|
+
### Find the Dev Server Command
|
|
26
|
+
```bash
|
|
27
|
+
# Check package.json scripts
|
|
28
|
+
cat package.json | grep -A 20 '"scripts"'
|
|
29
|
+
```
|
|
30
|
+
Common commands: `npm run dev`, `npm start`, `yarn dev`, `python manage.py runserver`
|
|
31
|
+
|
|
32
|
+
### Install Playwright
|
|
33
|
+
```bash
|
|
34
|
+
npm init -y # if no package.json
|
|
35
|
+
npm install -D @playwright/test
|
|
36
|
+
npx playwright install chromium
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If Playwright is already installed, check with:
|
|
40
|
+
```bash
|
|
41
|
+
npx playwright --version
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Start the Dev Server
|
|
45
|
+
Launch in background and wait for it:
|
|
46
|
+
```bash
|
|
47
|
+
npm run dev &
|
|
48
|
+
# Wait for server to be ready
|
|
49
|
+
sleep 5
|
|
50
|
+
curl -s http://localhost:3000 > /dev/null && echo "Server ready"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 2. Test Like a Real Human
|
|
56
|
+
|
|
57
|
+
**This is the most important section.** You are not writing API tests. You are simulating a real person sitting in front of a browser, moving their mouse, clicking things, typing, scrolling, and dragging.
|
|
58
|
+
|
|
59
|
+
### MANDATORY Rules
|
|
60
|
+
|
|
61
|
+
1. **Use real mouse movements.** Before clicking an element, move the mouse to it. Humans don't teleport-click.
|
|
62
|
+
```typescript
|
|
63
|
+
// BAD - robot click
|
|
64
|
+
await page.click('#submit');
|
|
65
|
+
|
|
66
|
+
// GOOD - human-like interaction
|
|
67
|
+
const button = page.locator('#submit');
|
|
68
|
+
await button.hover();
|
|
69
|
+
await button.click();
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
2. **Use real keyboard input.** Type character by character where it matters. Don't just set values.
|
|
73
|
+
```typescript
|
|
74
|
+
// BAD - robot input
|
|
75
|
+
await page.fill('#email', 'test@example.com');
|
|
76
|
+
|
|
77
|
+
// GOOD for testing input behavior - type like a human
|
|
78
|
+
await page.locator('#email').click();
|
|
79
|
+
await page.keyboard.type('test@example.com', { delay: 50 });
|
|
80
|
+
```
|
|
81
|
+
Note: `fill()` is fine for basic form filling. Use `keyboard.type()` when testing input validation, autocomplete, live search, or debounce behavior.
|
|
82
|
+
|
|
83
|
+
3. **Test gestures and drag interactions.** If the UI has drag-and-drop, sliders, resizable panels, sortable lists, or swipeable elements, you MUST test them with actual mouse drag sequences.
|
|
84
|
+
```typescript
|
|
85
|
+
// Drag and drop
|
|
86
|
+
const source = page.locator('.drag-item');
|
|
87
|
+
const target = page.locator('.drop-zone');
|
|
88
|
+
await source.dragTo(target);
|
|
89
|
+
|
|
90
|
+
// Manual drag for more control (slider, resize handle)
|
|
91
|
+
const slider = page.locator('.slider-thumb');
|
|
92
|
+
const box = await slider.boundingBox();
|
|
93
|
+
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
|
94
|
+
await page.mouse.down();
|
|
95
|
+
await page.mouse.move(box.x + 200, box.y + box.height / 2, { steps: 20 });
|
|
96
|
+
await page.mouse.up();
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
4. **Scroll like a human.** Use mouse wheel, not just `scrollIntoView`.
|
|
100
|
+
```typescript
|
|
101
|
+
// Scroll down the page
|
|
102
|
+
await page.mouse.wheel(0, 500);
|
|
103
|
+
|
|
104
|
+
// Scroll within a container
|
|
105
|
+
const container = page.locator('.scroll-container');
|
|
106
|
+
await container.hover();
|
|
107
|
+
await page.mouse.wheel(0, 300);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
5. **Hover over elements.** Test tooltips, dropdown menus, hover states.
|
|
111
|
+
```typescript
|
|
112
|
+
await page.locator('.menu-trigger').hover();
|
|
113
|
+
await expect(page.locator('.dropdown-menu')).toBeVisible();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
6. **Test tab navigation and focus.** Humans use Tab key to move between form fields.
|
|
117
|
+
```typescript
|
|
118
|
+
await page.locator('#first-name').click();
|
|
119
|
+
await page.keyboard.type('John');
|
|
120
|
+
await page.keyboard.press('Tab');
|
|
121
|
+
await page.keyboard.type('Doe'); // Now in next field
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
7. **Right-click where applicable.** Test context menus.
|
|
125
|
+
```typescript
|
|
126
|
+
await page.locator('.file-item').click({ button: 'right' });
|
|
127
|
+
await expect(page.locator('.context-menu')).toBeVisible();
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
8. **Double-click where applicable.** Test inline editing, file opening.
|
|
131
|
+
```typescript
|
|
132
|
+
await page.locator('.editable-cell').dblclick();
|
|
133
|
+
await expect(page.locator('.edit-input')).toBeVisible();
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Gesture & Interaction Patterns to Test
|
|
137
|
+
|
|
138
|
+
| UI Pattern | How a Human Uses It | Playwright Command |
|
|
139
|
+
|------------|-------------------|-------------------|
|
|
140
|
+
| Drag and drop | Click-hold, drag to target, release | `source.dragTo(target)` or manual `mouse.down/move/up` |
|
|
141
|
+
| Slider/range input | Drag the thumb left/right | `mouse.down()` → `mouse.move(x, y, {steps: 20})` → `mouse.up()` |
|
|
142
|
+
| Sortable list | Drag item to new position | `mouse.down()` → `mouse.move()` → `mouse.up()` |
|
|
143
|
+
| Resizable panel | Drag the resize handle | `mouse.down()` on handle → `mouse.move()` → `mouse.up()` |
|
|
144
|
+
| Dropdown menu | Click to open, click item | `trigger.click()` → `option.click()` |
|
|
145
|
+
| Hover menu | Mouse over trigger, click item | `trigger.hover()` → `menuItem.click()` |
|
|
146
|
+
| Carousel/slider | Click arrows or swipe | Arrow: `nextBtn.click()`. Swipe: `mouse.down/move/up` |
|
|
147
|
+
| Modal/dialog | Interact with content, close | Click content, then `closeBtn.click()` or press Escape |
|
|
148
|
+
| Toast/notification | Wait for it to appear and auto-dismiss | `expect(toast).toBeVisible()` → `expect(toast).not.toBeVisible({ timeout: 5000 })` |
|
|
149
|
+
| Infinite scroll | Scroll to bottom, wait for new content | `mouse.wheel(0, 1000)` → `expect(newItem).toBeVisible()` |
|
|
150
|
+
| File upload | Click upload area or drag file | `input.setInputFiles('path/to/file')` |
|
|
151
|
+
| Copy/paste | Select text, Ctrl+C, click target, Ctrl+V | `keyboard.press('Control+a')` → `keyboard.press('Control+c')` |
|
|
152
|
+
|
|
153
|
+
### Buttons vs Gestures: Test BOTH Separately
|
|
154
|
+
|
|
155
|
+
If the UI has both a button and a gesture to do the same thing (e.g., a delete button AND swipe-to-delete, a next button AND drag-to-advance), write **separate test cases** for each:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
test('delete item via button', async ({ page }) => {
|
|
159
|
+
await page.locator('.delete-btn').click();
|
|
160
|
+
await expect(page.locator('.item')).not.toBeVisible();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('delete item via swipe gesture', async ({ page }) => {
|
|
164
|
+
const item = page.locator('.item');
|
|
165
|
+
const box = await item.boundingBox();
|
|
166
|
+
await page.mouse.move(box.x + box.width - 20, box.y + box.height / 2);
|
|
167
|
+
await page.mouse.down();
|
|
168
|
+
await page.mouse.move(box.x + 20, box.y + box.height / 2, { steps: 15 });
|
|
169
|
+
await page.mouse.up();
|
|
170
|
+
await expect(item).not.toBeVisible();
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### What to Verify After Each Interaction
|
|
175
|
+
|
|
176
|
+
- Is the expected element visible/hidden? (`toBeVisible`, `not.toBeVisible`)
|
|
177
|
+
- Did the URL change? (`expect(page).toHaveURL(...)`)
|
|
178
|
+
- Did the text content update? (`toHaveText`, `toContainText`)
|
|
179
|
+
- Is the correct element focused? (`toBeFocused`)
|
|
180
|
+
- Did the screen go blank? (Take a screenshot: `page.screenshot()`)
|
|
181
|
+
- Does the same interaction work on multiple items? (Test 2-3 times)
|
|
182
|
+
- Does a partial gesture snap back correctly? (Drag halfway, release, verify original state)
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 3. Playwright Test Structure
|
|
187
|
+
|
|
188
|
+
### Basic Test File
|
|
189
|
+
```typescript
|
|
190
|
+
import { test, expect } from '@playwright/test';
|
|
191
|
+
|
|
192
|
+
test.describe('Feature Name', () => {
|
|
193
|
+
test.beforeEach(async ({ page }) => {
|
|
194
|
+
await page.goto('http://localhost:3000');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('should do something when user interacts', async ({ page }) => {
|
|
198
|
+
// Arrange - navigate to the right state
|
|
199
|
+
await page.locator('.nav-link').click();
|
|
200
|
+
|
|
201
|
+
// Act - interact like a human
|
|
202
|
+
await page.locator('#input-field').click();
|
|
203
|
+
await page.keyboard.type('hello world', { delay: 30 });
|
|
204
|
+
await page.locator('#submit-btn').hover();
|
|
205
|
+
await page.locator('#submit-btn').click();
|
|
206
|
+
|
|
207
|
+
// Assert - verify the result
|
|
208
|
+
await expect(page.locator('.success-message')).toBeVisible();
|
|
209
|
+
await expect(page.locator('.success-message')).toHaveText('Saved!');
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Playwright Config
|
|
215
|
+
```typescript
|
|
216
|
+
// playwright.config.ts
|
|
217
|
+
import { defineConfig } from '@playwright/test';
|
|
218
|
+
|
|
219
|
+
export default defineConfig({
|
|
220
|
+
testDir: './e2e',
|
|
221
|
+
timeout: 30000,
|
|
222
|
+
retries: 0,
|
|
223
|
+
use: {
|
|
224
|
+
baseURL: 'http://localhost:3000',
|
|
225
|
+
headless: true,
|
|
226
|
+
screenshot: 'only-on-failure',
|
|
227
|
+
trace: 'on-first-retry',
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Key Locator Strategies (Priority Order)
|
|
233
|
+
1. **Role-based** (most resilient): `page.getByRole('button', { name: 'Submit' })`
|
|
234
|
+
2. **Test ID**: `page.getByTestId('submit-btn')`
|
|
235
|
+
3. **Text content**: `page.getByText('Submit')`
|
|
236
|
+
4. **Label**: `page.getByLabel('Email address')`
|
|
237
|
+
5. **Placeholder**: `page.getByPlaceholder('Enter email')`
|
|
238
|
+
6. **CSS selector** (last resort): `page.locator('.btn-primary')`
|
|
239
|
+
|
|
240
|
+
### Useful Playwright APIs
|
|
241
|
+
```typescript
|
|
242
|
+
// Wait for element
|
|
243
|
+
await page.locator('.item').waitFor({ state: 'visible', timeout: 10000 });
|
|
244
|
+
|
|
245
|
+
// Wait for navigation
|
|
246
|
+
await page.waitForURL('**/dashboard');
|
|
247
|
+
|
|
248
|
+
// Wait for network idle (page fully loaded)
|
|
249
|
+
await page.waitForLoadState('networkidle');
|
|
250
|
+
|
|
251
|
+
// Screenshot for debugging
|
|
252
|
+
await page.screenshot({ path: 'debug.png', fullPage: true });
|
|
253
|
+
|
|
254
|
+
// Get element count
|
|
255
|
+
const count = await page.locator('.list-item').count();
|
|
256
|
+
|
|
257
|
+
// Check element attribute
|
|
258
|
+
await expect(page.locator('#btn')).toHaveAttribute('disabled', '');
|
|
259
|
+
|
|
260
|
+
// Check CSS property
|
|
261
|
+
await expect(page.locator('.box')).toHaveCSS('opacity', '1');
|
|
262
|
+
|
|
263
|
+
// Viewport resize (test responsive)
|
|
264
|
+
await page.setViewportSize({ width: 375, height: 667 });
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## 4. Debugging Failing Tests
|
|
270
|
+
|
|
271
|
+
### Common Errors
|
|
272
|
+
|
|
273
|
+
| Error | Likely Cause | Fix |
|
|
274
|
+
|-------|-------------|-----|
|
|
275
|
+
| Element not found | Wrong selector or element not rendered yet | Use `waitFor`, check selector with `page.locator().count()` |
|
|
276
|
+
| Timeout | Page didn't load or element never appeared | Check if dev server is running, increase timeout |
|
|
277
|
+
| Element not clickable | Covered by another element (modal, overlay) | Close overlays first, or use `force: true` as last resort |
|
|
278
|
+
| Navigation timeout | SPA route change not detected | Use `waitForURL` with glob pattern |
|
|
279
|
+
| Flaky test | Timing issue, animation not complete | Add `waitForLoadState`, explicit waits |
|
|
280
|
+
|
|
281
|
+
### Debug Workflow
|
|
282
|
+
|
|
283
|
+
1. **Run with headed browser:**
|
|
284
|
+
```bash
|
|
285
|
+
npx playwright test --headed
|
|
286
|
+
```
|
|
287
|
+
2. **Run with debug mode (step through):**
|
|
288
|
+
```bash
|
|
289
|
+
npx playwright test --debug
|
|
290
|
+
```
|
|
291
|
+
3. **Generate trace for failed tests:**
|
|
292
|
+
```bash
|
|
293
|
+
npx playwright test --trace on
|
|
294
|
+
npx playwright show-trace trace.zip
|
|
295
|
+
```
|
|
296
|
+
4. **Take screenshots at failure points** in the test:
|
|
297
|
+
```typescript
|
|
298
|
+
await page.screenshot({ path: 'debug-step-3.png' });
|
|
299
|
+
```
|
|
300
|
+
5. **Inspect what's on screen:**
|
|
301
|
+
```typescript
|
|
302
|
+
console.log(await page.content()); // HTML dump
|
|
303
|
+
console.log(await page.locator('body').innerText()); // Text content
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### When Tests Are Flaky
|
|
307
|
+
- Add `await page.waitForLoadState('networkidle')` after navigation
|
|
308
|
+
- Use `await page.locator('.element').waitFor()` before interacting
|
|
309
|
+
- Check for animations: add `await page.waitForTimeout(300)` ONLY after confirming animation duration
|
|
310
|
+
- Ensure test isolation: each test starts from a clean state
|
|
311
|
+
- Check for race conditions: server response might arrive before or after UI update
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## 5. Verification Protocol
|
|
316
|
+
|
|
317
|
+
Before declaring success, you MUST run all tests **3 consecutive times** and all 3 must pass:
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
npx playwright test && npx playwright test && npx playwright test
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
- If any run fails, investigate and fix, then restart the 3-run verification.
|
|
324
|
+
- Do NOT count a run that was manually restarted.
|
|
325
|
+
- Report the pass/fail result of each run in your output.
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## 6. Test Organization
|
|
330
|
+
|
|
331
|
+
Place tests in an `e2e/` directory:
|
|
332
|
+
```
|
|
333
|
+
e2e/
|
|
334
|
+
auth.spec.ts # Login, register, logout
|
|
335
|
+
navigation.spec.ts # Page routing, links, back/forward
|
|
336
|
+
forms.spec.ts # Input, validation, submission
|
|
337
|
+
gestures.spec.ts # Drag, drop, swipe, resize
|
|
338
|
+
responsive.spec.ts # Mobile/tablet/desktop viewports
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Name tests descriptively:
|
|
342
|
+
```typescript
|
|
343
|
+
test('user can drag task card from Todo to Done column', ...);
|
|
344
|
+
test('slider updates price filter when dragged right', ...);
|
|
345
|
+
test('left-swiping a card dismisses it and shows next card', ...);
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## 7. Exit Signals
|
|
351
|
+
|
|
352
|
+
Use the same DaveLoop exit signals:
|
|
353
|
+
|
|
354
|
+
- `[DAVELOOP:RESOLVED]` - All tests pass 3 consecutive times. Task complete.
|
|
355
|
+
- `[DAVELOOP:BLOCKED]` - Cannot proceed (e.g., no dev server, missing dependencies, app won't build).
|
|
356
|
+
- `[DAVELOOP:CLARIFY]` - Need information from user (e.g., which page to test, login credentials, base URL).
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## 8. Reasoning Protocol
|
|
361
|
+
|
|
362
|
+
Before each action, use the DaveLoop reasoning format:
|
|
363
|
+
|
|
364
|
+
```
|
|
365
|
+
=== DAVELOOP REASONING ===
|
|
366
|
+
KNOWN: What you know about the current state (server status, test results, UI state)
|
|
367
|
+
UNKNOWN: What you still need to figure out
|
|
368
|
+
HYPOTHESIS: Your theory about what to do next
|
|
369
|
+
NEXT ACTION: The specific command or edit you'll make
|
|
370
|
+
WHY: Why this action will move toward the goal
|
|
371
|
+
===========================
|
|
372
|
+
```
|
|
@@ -13,7 +13,7 @@ long_description = readme_file.read_text(encoding="utf-8") if readme_file.exists
|
|
|
13
13
|
|
|
14
14
|
setup(
|
|
15
15
|
name="daveloop",
|
|
16
|
-
version="1.
|
|
16
|
+
version="1.4.0",
|
|
17
17
|
description="Self-healing debug agent powered by Claude Code CLI",
|
|
18
18
|
long_description=long_description,
|
|
19
19
|
long_description_content_type="text/markdown",
|
|
@@ -32,7 +32,7 @@ setup(
|
|
|
32
32
|
},
|
|
33
33
|
include_package_data=True,
|
|
34
34
|
package_data={
|
|
35
|
-
"": ["daveloop_prompt.md"],
|
|
35
|
+
"": ["daveloop_prompt.md", "daveloop_maestro_prompt.md", "daveloop_web_prompt.md"],
|
|
36
36
|
},
|
|
37
37
|
classifiers=[
|
|
38
38
|
"Development Status :: 4 - Beta",
|
daveloop-1.3.0/MANIFEST.in
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|