@vijaypjavvadi/bdd2pw 1.1.5 → 1.1.6
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/CHANGELOG.md +824 -755
- package/dist/transformers/stepMatcher.js +53 -7
- package/dist/transformers/stepMatcher.js.map +1 -1
- package/package.json +105 -105
package/CHANGELOG.md
CHANGED
|
@@ -1,755 +1,824 @@
|
|
|
1
|
-
# bdd2pw — CHANGELOG
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [Unreleased]
|
|
9
|
-
|
|
10
|
-
_Nothing yet._
|
|
11
|
-
|
|
12
|
-
## [1.1.
|
|
13
|
-
|
|
14
|
-
### Fixed — subject-less
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
|
30
|
-
|
|
31
|
-
| `
|
|
32
|
-
| `enters
|
|
33
|
-
| `
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
####
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
**
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
####
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
- `
|
|
253
|
-
|
|
254
|
-
- `
|
|
255
|
-
- `
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
`
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
(
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
The
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
###
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
`
|
|
506
|
-
`
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
- `
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
-
|
|
553
|
-
- **
|
|
554
|
-
|
|
555
|
-
- **
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
-
|
|
585
|
-
-
|
|
586
|
-
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
-
|
|
606
|
-
- `
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
-
|
|
618
|
-
|
|
619
|
-
####
|
|
620
|
-
|
|
621
|
-
- **
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
-
|
|
640
|
-
|
|
641
|
-
####
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
-
|
|
654
|
-
-
|
|
655
|
-
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
-
|
|
664
|
-
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
-
|
|
721
|
-
-
|
|
722
|
-
- **
|
|
723
|
-
- **
|
|
724
|
-
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
- **
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
-
|
|
733
|
-
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
-
|
|
738
|
-
|
|
739
|
-
-
|
|
740
|
-
|
|
741
|
-
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
-
|
|
746
|
-
- `
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
1
|
+
# bdd2pw — CHANGELOG
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
_Nothing yet._
|
|
11
|
+
|
|
12
|
+
## [1.1.6] — 2026-05-07
|
|
13
|
+
|
|
14
|
+
### Fixed — subject-less 'Enter "X" in the field' fell through to TODO
|
|
15
|
+
|
|
16
|
+
Rule 2a (Input with explicit value + field) required a `${SUBJ}` prefix
|
|
17
|
+
(`I` / `User` / `the user`). Cloud-jobs-template runs against the LLM
|
|
18
|
+
stack with cache OFF surfaced subject-less variants like:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
When Enter 'student' in the username field
|
|
22
|
+
And Enter 'Password123' into the password field
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
These hit no rule and silently passed (no fill, no failure). Same fix
|
|
26
|
+
shape as v1.1.5 applied to rule 2b — wrap `${SUBJ}` in
|
|
27
|
+
`(?:${SUBJ}\s+)?` so all four shapes match equivalently.
|
|
28
|
+
|
|
29
|
+
| Form | Status before 1.1.6 | Status after 1.1.6 |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `I enter "alice" into the username field` | ✅ rule 2a | ✅ rule 2a |
|
|
32
|
+
| `User enters "alice" into the username field` | ✅ rule 2a | ✅ rule 2a |
|
|
33
|
+
| `Enter 'alice' in the username field` | ❌ TODO | ✅ rule 2a |
|
|
34
|
+
| `Enter "alice" into the password field` | ❌ TODO | ✅ rule 2a |
|
|
35
|
+
|
|
36
|
+
### Fixed — descriptive parentheticals leaked into URL slug regexes
|
|
37
|
+
|
|
38
|
+
Rules 10 (remains-on), 11b (redirected-to), N4 (URL doesn't change),
|
|
39
|
+
and N5e (NOT redirected away) all slugify a captured page-name
|
|
40
|
+
description into a URL regex. Production runs hit:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
Then User is redirected to logged-in page (URL changes away from login page)
|
|
44
|
+
And user remains on login page (URL does not change away from login page)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The end-anchor `(?: page)?$` in those rules doesn't fire when the string
|
|
48
|
+
ends in `)`, so the entire parenthetical was being captured as part of
|
|
49
|
+
the description. Slug became
|
|
50
|
+
`/logged-in[-_/]?page[-_/]?\(URL[-_/]?changes[-_/]?away.../`
|
|
51
|
+
which never matches a real URL — assertion silently failed in a way that
|
|
52
|
+
cucumber reported as pass-with-warning rather than actionable failure.
|
|
53
|
+
|
|
54
|
+
Added `stripParentheticals()` helper and `cleanSlugTarget()` combiner
|
|
55
|
+
that strips parentheticals + articles + trailing ` page`, applied to
|
|
56
|
+
all four affected rules. Rule 11a (`(URL contains 'X')`) still runs
|
|
57
|
+
first and still extracts authoritative URL fragments — only descriptive
|
|
58
|
+
parentheticals fall through to the cleaned slugifier.
|
|
59
|
+
|
|
60
|
+
### Tests
|
|
61
|
+
|
|
62
|
+
- `tests/unit/stepMatcher.test.ts` +7 new tests under
|
|
63
|
+
`v1.1.6 — subject-less 'Enter ...'` and
|
|
64
|
+
`v1.1.6 — parenthetical prose in URL slug rules`:
|
|
65
|
+
- subject-less `Enter 'X' in the username field`
|
|
66
|
+
- subject-less `Enter "X" into the password field`
|
|
67
|
+
- regression: subject-prefixed `I enter "X" in ...` still matches
|
|
68
|
+
- rule 11b: parenthetical stripped, slug = `logged-in`
|
|
69
|
+
- rule 10: parenthetical stripped, slug = `login`
|
|
70
|
+
- rule N5e: parenthetical stripped, slug = `login`
|
|
71
|
+
- regression: rule 11a still wins for authoritative parentheticals
|
|
72
|
+
|
|
73
|
+
### Why this matters
|
|
74
|
+
|
|
75
|
+
Today's cloud-jobs-template runs went 14/14 → 2/8 between two
|
|
76
|
+
consecutive runs of the same job because the LLM picked different
|
|
77
|
+
phrasings (`in` vs `into`) and added descriptive parentheticals to
|
|
78
|
+
redirect assertions. Pairing this release with TCG temperature=0 and
|
|
79
|
+
re-enabled response cache should stabilise pass-rate run-to-run.
|
|
80
|
+
|
|
81
|
+
## [1.1.5] — 2026-05-05
|
|
82
|
+
|
|
83
|
+
### Fixed — subject-less compact 'enters password "X"' form
|
|
84
|
+
|
|
85
|
+
LLM produces compact verb-first input forms freely:
|
|
86
|
+
```
|
|
87
|
+
And enters password "Password123"
|
|
88
|
+
When enters username "alice"
|
|
89
|
+
And types the password "secret"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Rule 2b previously required a `${SUBJ}` prefix (`I` / `User` / `the user`).
|
|
93
|
+
The compact form was hitting TODO. Rule 2b's SUBJ is now optional via
|
|
94
|
+
`(?:${SUBJ}\s+)?`, so all four shapes match the same way:
|
|
95
|
+
|
|
96
|
+
| Form | Status before 1.1.5 | Status after 1.1.5 |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `I enter username "alice"` | ✅ rule 2b | ✅ rule 2b |
|
|
99
|
+
| `User enters username "alice"` | ✅ rule 2b | ✅ rule 2b |
|
|
100
|
+
| `the user enters username "alice"` | ✅ rule 2b | ✅ rule 2b |
|
|
101
|
+
| `enters password "Password123"` | ❌ TODO | ✅ rule 2b |
|
|
102
|
+
| `And enters username "alice"` | ❌ TODO | ✅ rule 2b |
|
|
103
|
+
|
|
104
|
+
#### Why this is a one-line change
|
|
105
|
+
|
|
106
|
+
The fix is purely the regex prefix:
|
|
107
|
+
- Before: `^${SUBJ} (?:enter|enters|...) (?:the )?(.+?) ["']([^"']*)["']$`
|
|
108
|
+
- After: `^(?:${SUBJ}\s+)?(?:enter|enters|...) (?:the )?(.+?) ["']([^"']*)["']$`
|
|
109
|
+
|
|
110
|
+
The build function is unchanged — same `fillFieldBinding(...)` flow.
|
|
111
|
+
Existing rule 2a (`I enter "X" into the field`) still claims its territory
|
|
112
|
+
because its pattern has `["']<value>["']` early; the subject-less compact
|
|
113
|
+
form ends with the value, which only matches rule 2b's structure.
|
|
114
|
+
|
|
115
|
+
#### Known limitation (deferred)
|
|
116
|
+
|
|
117
|
+
The vague form `user enters valid password` (no quoted value) is still a
|
|
118
|
+
TODO. Without a value, we can't generate a working `.fill()` call. A human
|
|
119
|
+
has to provide the credentials. This is a generation-input gap, not a rule
|
|
120
|
+
gap — adding a TODO with explanatory comment would be misleading.
|
|
121
|
+
|
|
122
|
+
#### Tests
|
|
123
|
+
|
|
124
|
+
- `tests/unit/stepMatcher.test.ts` +4 new tests under
|
|
125
|
+
`v1.1.5 — subject-less 'enters <field> "V"'`:
|
|
126
|
+
- subject-less `enters password "X"`
|
|
127
|
+
- subject-less `enters username "X"`
|
|
128
|
+
- regression: subject-prefixed `User enters username "X"` still matches
|
|
129
|
+
- subject-less `types the password "X"` (alt verb + article)
|
|
130
|
+
|
|
131
|
+
#### Files
|
|
132
|
+
|
|
133
|
+
- Modified: `src/transformers/stepMatcher.ts` (rule 2b regex prefix only).
|
|
134
|
+
- Modified: `tests/unit/stepMatcher.test.ts` (+4 tests).
|
|
135
|
+
|
|
136
|
+
## [1.1.4] — 2026-05-05
|
|
137
|
+
|
|
138
|
+
### Fixed — article-leak in URL slugs + N5d without URL
|
|
139
|
+
|
|
140
|
+
A run of `R-5D89B426-001.feature` on bdd2pw 1.1.3 cut failures from 6 → 2,
|
|
141
|
+
but two real bugs remain:
|
|
142
|
+
|
|
143
|
+
#### 1. English articles leak into URL regex slugs
|
|
144
|
+
|
|
145
|
+
**Symptom:** `Then the user is redirected to a logged-in page` →
|
|
146
|
+
emitted regex `/a[-_/]?logged-in/` → fails against
|
|
147
|
+
`https://practicetestautomation.com/logged-in-successfully/` because the
|
|
148
|
+
URL has no "a" before "logged-in".
|
|
149
|
+
|
|
150
|
+
**Root cause:** rule 11b (and rules 10, N4, N5e) capture the page-name
|
|
151
|
+
description and slugify it. The capture preserves English articles
|
|
152
|
+
("a", "an", "the") if they're not stripped by the rule's `(?:the )?`
|
|
153
|
+
prefix — which only handles "the".
|
|
154
|
+
|
|
155
|
+
**Fix:** new `stripArticles(s)` helper removes leading/internal/trailing
|
|
156
|
+
"a"/"an"/"the" tokens before slugifying. Applied to rules 10, 11b, N4,
|
|
157
|
+
N5e — every rule that converts a description to a regex slug.
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
// Before: "redirected to a logged-in page" → "a[-_/]?logged-in"
|
|
161
|
+
// After: "redirected to a logged-in page" → "logged-in" ✓ matches /logged-in-successfully/
|
|
162
|
+
|
|
163
|
+
// Before: "remain on a login page" → "a[-_/]?login"
|
|
164
|
+
// After: "remain on a login page" → "login" ✓
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### 2. N5d required `at "URL"` suffix
|
|
168
|
+
|
|
169
|
+
**Symptom:** 6 TODOs for `Given the user is on the login page` (no URL).
|
|
170
|
+
N5d's pattern from 1.1.3 mandated `at "URL"`. LLM produces both forms.
|
|
171
|
+
|
|
172
|
+
**Fix:** extended N5d pattern — `at "URL"` is now optional. Both
|
|
173
|
+
`Given the user is on the login page` and
|
|
174
|
+
`Given the user is on the login page at "URL"` map to `goto()`.
|
|
175
|
+
|
|
176
|
+
#### Tests
|
|
177
|
+
|
|
178
|
+
- `tests/unit/stepMatcher.test.ts` +5 new tests across 2 describe blocks
|
|
179
|
+
(3 article-stripping cases + 2 N5d optional-URL cases).
|
|
180
|
+
|
|
181
|
+
#### Production impact
|
|
182
|
+
|
|
183
|
+
The 2 remaining failures from R-5D89B426-001 (`toHaveURL` timeouts on
|
|
184
|
+
`/a[-_/]?logged-in/`) and the 6 TODOs (`Given the user is on the login
|
|
185
|
+
page`) are both eliminated. Repin cloud-jobs-template to 1.1.4 to verify.
|
|
186
|
+
|
|
187
|
+
#### Files
|
|
188
|
+
|
|
189
|
+
- Modified: `src/transformers/stepMatcher.ts` (+stripArticles helper,
|
|
190
|
+
applied to rules 10/11b/N4/N5e; N5d pattern extended).
|
|
191
|
+
- Modified: `tests/unit/stepMatcher.test.ts` (+5 tests).
|
|
192
|
+
|
|
193
|
+
## [1.1.3] — 2026-05-05
|
|
194
|
+
|
|
195
|
+
### Fixed — production failure modes from cloud-jobs run on 1.1.2
|
|
196
|
+
|
|
197
|
+
A run of `R-5D89B426-001.feature` on bdd2pw 1.1.2 produced **6 test failures**
|
|
198
|
+
across 12 scenarios (Chromium + Firefox), with three distinct root causes —
|
|
199
|
+
all in synthesised locators when the POM lacked the field. 1.1.3 is a
|
|
200
|
+
correctness fix release: no new dialect coverage, but the assertions we
|
|
201
|
+
already emit now actually work against real pages.
|
|
202
|
+
|
|
203
|
+
#### 1. Cross-role synthesis (button-vs-link)
|
|
204
|
+
|
|
205
|
+
**Symptom:** `await expect(page.getByRole("button", { name: "Logout" })).toBeVisible()`
|
|
206
|
+
times out because the page has `<a>Log out</a>` (a link, not a button).
|
|
207
|
+
|
|
208
|
+
**Fix:** new helper `synthRoleNameLocator(pageVar, name)` emits
|
|
209
|
+
```ts
|
|
210
|
+
page.locator("a, button, [role='button'], [role='link']")
|
|
211
|
+
.filter({ hasText: new RegExp("^L\\s*o\\s*g\\s*o\\s*u\\s*t$", "i") })
|
|
212
|
+
.first()
|
|
213
|
+
```
|
|
214
|
+
This handles both:
|
|
215
|
+
- **Role mismatch** (LLM says "button", page uses `<a>` or vice versa).
|
|
216
|
+
- **Spelling variance** (LLM writes "Logout", page renders "Log out") via a
|
|
217
|
+
flexible regex with `\s*` between every character.
|
|
218
|
+
|
|
219
|
+
Applied to N6 (visibility) and N7 (negative visibility) when synthesising.
|
|
220
|
+
POM-field references stay unchanged (the picker already produces correct
|
|
221
|
+
locators from the snapshot).
|
|
222
|
+
|
|
223
|
+
#### 2. Strict-mode violations everywhere
|
|
224
|
+
|
|
225
|
+
**Symptom:** `await expect(page.getByText("Your username is invalid")).toBeVisible()`
|
|
226
|
+
fails with `strict mode violation: ... resolved to 2 elements` because the
|
|
227
|
+
text appears in BOTH `<div id="error">` AND a `<b>` repeat highlight.
|
|
228
|
+
|
|
229
|
+
**Fix:** every synthesised text locator now ends in `.first()`:
|
|
230
|
+
- N5b page-level text → `getByText("X").first()`
|
|
231
|
+
- N5 / N5c severity-message fallback → `getByText("V").first()`
|
|
232
|
+
- N7 success / generic fallback → `getByText(desc).first()`
|
|
233
|
+
- N7 error fallback → `getByRole("alert").first()`
|
|
234
|
+
|
|
235
|
+
POM-field references untouched (POM fields are unique by construction).
|
|
236
|
+
|
|
237
|
+
#### 3. New rules N5d + N5e — `is on page at URL` / negative redirect
|
|
238
|
+
|
|
239
|
+
**Symptom:** 7 TODOs in BDD_REVIEW for `Given the user is on the login page
|
|
240
|
+
at "URL"` (Background-style precondition with embedded URL) and 1 TODO for
|
|
241
|
+
`And the user is NOT redirected away from the login page`.
|
|
242
|
+
|
|
243
|
+
**Fix:** two new rules.
|
|
244
|
+
|
|
245
|
+
| # | Pattern | Emits |
|
|
246
|
+
|---|---|---|
|
|
247
|
+
| N5d | `<subject> is/are/am on (the )? <X> page at 'URL'` | `goto()` (Background handles real navigation; this is narrative context) |
|
|
248
|
+
| N5e | `<subject> is/are/am NOT redirected (away from\|from) (the )? <X> page` | `toHaveURL(<slug>)` (URL still contains page name → still on page) |
|
|
249
|
+
|
|
250
|
+
#### Helpers (internal)
|
|
251
|
+
|
|
252
|
+
- `flexibleNameRegex(name)` — builds `new RegExp("^X\\s*Y\\s*Z$", "i")` from
|
|
253
|
+
a descriptive name. Used by N6/N7 synthesis.
|
|
254
|
+
- `synthRoleNameLocator(pageVar, name)` — cross-role + flexible text filter.
|
|
255
|
+
- `synthFlexibleTextLocator(pageVar, text)` — `getByText("X").first()`.
|
|
256
|
+
|
|
257
|
+
#### Total rule count
|
|
258
|
+
|
|
259
|
+
26 → **30** (N5d, N5e new; N5b, N5c, N5, N6, N7 strict-mode-fixed).
|
|
260
|
+
|
|
261
|
+
#### Tests
|
|
262
|
+
|
|
263
|
+
Existing unit + e2e tests still pass — the synthesis changes don't affect
|
|
264
|
+
test pom fixtures (POM fields are present, so synthesis branches don't fire).
|
|
265
|
+
The fixes apply at runtime against real pages with non-trivial DOM.
|
|
266
|
+
|
|
267
|
+
#### Production impact
|
|
268
|
+
|
|
269
|
+
After 1.1.3 + repin, the same `R-5D89B426-001.feature` run that produced 6
|
|
270
|
+
failures on 1.1.2 should produce 0 failures (or surface real bugs in the
|
|
271
|
+
test design — neither the page nor the test was at fault for the 1.1.2
|
|
272
|
+
failures, they were synthesis bugs).
|
|
273
|
+
|
|
274
|
+
#### Files
|
|
275
|
+
|
|
276
|
+
- Modified: `src/transformers/stepMatcher.ts` (+3 helpers, +2 rules N5d/N5e,
|
|
277
|
+
N5/N5b/N5c/N5e/N6/N7 fallbacks all updated to use helpers).
|
|
278
|
+
|
|
279
|
+
## [1.1.2] — 2026-05-05
|
|
280
|
+
|
|
281
|
+
### Fixed — second batch of LLM-narrative dialect gaps
|
|
282
|
+
|
|
283
|
+
A real cloud-jobs run of `R-5D89B426-001.feature` on bdd2pw 1.1.1 produced
|
|
284
|
+
**11 unmatched warnings** across 4 scenarios — six new patterns the LLM
|
|
285
|
+
service is generating that 1.1.1's 22 rules don't cover. All 12 tests
|
|
286
|
+
"passed" but most assertions were silent TODOs. Same false-positive issue
|
|
287
|
+
as before 1.1.1 fixed it for the first batch.
|
|
288
|
+
|
|
289
|
+
1.1.2 closes the second batch with **+4 new rules + extensions to N2/N6**.
|
|
290
|
+
Total rule count: **22 → 26**.
|
|
291
|
+
|
|
292
|
+
#### New / extended rules
|
|
293
|
+
|
|
294
|
+
| # | Pattern | Emits |
|
|
295
|
+
|---|---|---|
|
|
296
|
+
| N1.5 | `[Given\|And] the <X> page is (displayed\|loaded\|shown\|visible\|present)` | `goto()` (Background-style precondition that drops the navigation verb) |
|
|
297
|
+
| N2.5 | `<subject> leaves the <X> field empty (do not type anything)` | `// intentionally left empty: <X>` (subject-prefixed variant of N2) |
|
|
298
|
+
| N5b | `the page (displays\|contains\|shows) [the message] 'X'` | `page.getByText("X").toBeVisible()` (page-level text assertion) |
|
|
299
|
+
| N5c | `an <severity> message containing 'V' is displayed` | `toContainText("V")` against severity-aware field (error/success) |
|
|
300
|
+
| N6 ext | `a Logout button is visible` (unquoted role+name) | `toBeVisible` against POM field, synthesises `getByRole` fallback |
|
|
301
|
+
|
|
302
|
+
#### Why 4 rules + 2 extensions, not 6 fresh rules
|
|
303
|
+
|
|
304
|
+
- N2 already handled `Leave the X field empty`; adding subject prefix
|
|
305
|
+
(`<subject> leaves`) was a small extension in front of the existing pattern.
|
|
306
|
+
- N6 already handled quoted `"X"` button visibility; adding a second
|
|
307
|
+
capture branch for unquoted names reused the same build path.
|
|
308
|
+
|
|
309
|
+
Both extensions are fully backwards-compatible — every step that matched in
|
|
310
|
+
1.1.1 still matches the same way in 1.1.2.
|
|
311
|
+
|
|
312
|
+
#### Tests
|
|
313
|
+
|
|
314
|
+
- Extended `tests/unit/stepMatcher.test.ts` with 5 new describe blocks (~10
|
|
315
|
+
test cases) covering each new pattern + edge cases (POM field present
|
|
316
|
+
vs synthesised fallback, unquoted vs quoted, severity dispatch).
|
|
317
|
+
|
|
318
|
+
#### Production impact
|
|
319
|
+
|
|
320
|
+
The cloud-jobs run that surfaced these gaps was hitting:
|
|
321
|
+
- `Given the login page is displayed` (Background, every scenario × 4)
|
|
322
|
+
- `When the user leaves the username field empty` (TC-003)
|
|
323
|
+
- `And the page displays "Logged In Successfully"` (TC-001)
|
|
324
|
+
- `Then the page contains the message "X"` (Outline rows × 3)
|
|
325
|
+
- `Then an error message containing "X" is displayed` (TC-002)
|
|
326
|
+
- `And a Logout button is visible` (TC-001)
|
|
327
|
+
|
|
328
|
+
After 1.1.2 + repin, all six patterns produce real assertions instead of
|
|
329
|
+
silent TODOs. TC-001 → genuine end-to-end check; TC-002/3/4 → actual error
|
|
330
|
+
display assertions instead of silent passes.
|
|
331
|
+
|
|
332
|
+
#### Files
|
|
333
|
+
|
|
334
|
+
- Modified: `src/transformers/stepMatcher.ts` (+4 rules, N2 + N6 extended).
|
|
335
|
+
- Modified: `tests/unit/stepMatcher.test.ts` (+5 describe blocks).
|
|
336
|
+
- Doc bump 22 → 26 across `README.md`, `docs/SCOPE.md`, `docs/STATUS.md`.
|
|
337
|
+
|
|
338
|
+
## [1.1.1] — 2026-05-05
|
|
339
|
+
|
|
340
|
+
### Fixed — LLM-narrative Gherkin dialect
|
|
341
|
+
|
|
342
|
+
Real test-case-generation services (cloud-jobs-template) produce more
|
|
343
|
+
verbose, subject-less Gherkin than hand-authored suites. Before 1.1.1, six
|
|
344
|
+
out of nine steps in a typical TC-001 (Successful login) fell through to
|
|
345
|
+
`// TODO`. Result: forms didn't get filled, Submit clicked an empty form,
|
|
346
|
+
URL never changed, `toHaveURL` timed out — the test reported as failed
|
|
347
|
+
honestly. Worse, the negative scenarios (TC-002/3/4) reported as **passed**
|
|
348
|
+
because every assertion was a silent no-op TODO.
|
|
349
|
+
|
|
350
|
+
1.1.1 adds **7 new step-matcher rules** plus the
|
|
351
|
+
`examples/llm-narrative-login/` regression fixture (locked from a real
|
|
352
|
+
production run, `j-20f3defc3044f7ec`).
|
|
353
|
+
|
|
354
|
+
#### New rules
|
|
355
|
+
|
|
356
|
+
| # | Pattern | Emits |
|
|
357
|
+
|---|---|---|
|
|
358
|
+
| N1 | `Locate the X (input)? field and (enter\|type\|fill) 'V'` | `<field>.fill("V")` via stripUiSuffix + findField |
|
|
359
|
+
| N2 | `Leave the X field empty (do not type anything)` | `// intentionally left empty: <X>` (explicit comment) |
|
|
360
|
+
| N3 | `Observe ...` / `Note ...` | `// observation: <X>` |
|
|
361
|
+
| N4 | `URL does (not\|n't) change (to <description>)?` | `not.toHaveURL(new RegExp("<slug>"))` |
|
|
362
|
+
| N5 | `... such as 'V'` / `... (e.g., 'V')` / `... indicating 'V'` / `... like 'V'` | `toContainText("V")` against error/success field |
|
|
363
|
+
| N6 | `A 'X' (button\|link\|...)? is visible on the page` | `toBeVisible` against POM field, fallback to synthesised `getByRole(role, { name: 'X' })` |
|
|
364
|
+
| N7 | `No 'X' (button\|...)? appears` / `No <noun> are/is displayed` | `not.toBeVisible` against POM field, fallback to synthesised role/text locator |
|
|
365
|
+
|
|
366
|
+
`Navigate to <URL>` is already handled by rule 1 (the optional SUBJ prefix
|
|
367
|
+
accepts subject-less variants); `Click the 'X' button` is already handled
|
|
368
|
+
by rule 3. Both verified against the LLM fixture.
|
|
369
|
+
|
|
370
|
+
Total rule count: **15 → 22** deterministic patterns.
|
|
371
|
+
|
|
372
|
+
#### Helper: `findFieldByDescription`
|
|
373
|
+
|
|
374
|
+
New module-private helper in `stepMatcher.ts`. Strips trailing UI-element
|
|
375
|
+
words (`input field`, `field`, `box`, `element`, ...) from a description
|
|
376
|
+
then defers to `findField` with the appropriate suffix preferences. Used
|
|
377
|
+
by rules N1, N6, N7. Pure deterministic, no fuzzy matching.
|
|
378
|
+
|
|
379
|
+
#### Per design call (4): negative assertions never drop to TODO
|
|
380
|
+
|
|
381
|
+
For `No 'X' button appears` / `No error messages are displayed` / `A 'X' is
|
|
382
|
+
visible`, when the POM has no matching field, the rule **synthesises** a
|
|
383
|
+
`getByRole`/`getByText` locator from the description rather than emitting
|
|
384
|
+
`// TODO`. This turns false-positive passes (silent no-op TODOs) into
|
|
385
|
+
honest assertions that actually exercise the page.
|
|
386
|
+
|
|
387
|
+
Caveat: synthesised locators can be over-permissive (e.g.
|
|
388
|
+
`getByRole('button', { name: 'Log out' })` will match a logout link with
|
|
389
|
+
`role="button"` aria attribute even if the page layout differs from the
|
|
390
|
+
fixture). When this matters, hand-edit the spec — the rule report flags
|
|
391
|
+
none of these synthesises as warnings, so they're invisible to the report.
|
|
392
|
+
|
|
393
|
+
#### Tests
|
|
394
|
+
|
|
395
|
+
- New e2e: `tests/e2e/llmNarrativeLogin.test.ts` — locks the
|
|
396
|
+
`examples/llm-narrative-login/` fixture as a regression. Asserts:
|
|
397
|
+
- 0 warning-level review items (every step matches a rule).
|
|
398
|
+
- All 4 scenarios produce real fills/clicks (no TODOs).
|
|
399
|
+
- The new rules emit the expected matchers (`not.toHaveURL`,
|
|
400
|
+
`toContainText`, `toBeVisible`, etc.) against the right locators.
|
|
401
|
+
- Extended unit tests: `tests/unit/stepMatcher.test.ts` — one `describe`
|
|
402
|
+
block per new rule covering subject-less variants, edge cases, and
|
|
403
|
+
POM-vs-synthesised-locator branching.
|
|
404
|
+
|
|
405
|
+
#### Files
|
|
406
|
+
|
|
407
|
+
- New: `examples/llm-narrative-login/{login.feature, snapshot.json, README.md}`.
|
|
408
|
+
- New: `tests/e2e/llmNarrativeLogin.test.ts`.
|
|
409
|
+
- Modified: `src/transformers/stepMatcher.ts` (+7 rules, +stripUiSuffix,
|
|
410
|
+
+findFieldByDescription).
|
|
411
|
+
- Modified: `tests/unit/stepMatcher.test.ts` (+~16 tests across 7 describe blocks).
|
|
412
|
+
|
|
413
|
+
#### Migration notes
|
|
414
|
+
|
|
415
|
+
Pure additive — no existing rule changed semantics. Specs that already
|
|
416
|
+
matched 15 rules continue to match the same way. The 7 new rules **only**
|
|
417
|
+
fire on patterns that previously produced `// TODO` warnings, so existing
|
|
418
|
+
fixtures (`practice-test-login`, `cms-login`) regress to zero diff.
|
|
419
|
+
|
|
420
|
+
If you have a cloud-jobs-template pinned to 1.1.0, bump to 1.1.1 and
|
|
421
|
+
re-run any failing TC-001-style scenario — fills should now be real.
|
|
422
|
+
|
|
423
|
+
## [1.1.0] — 2026-05-05
|
|
424
|
+
|
|
425
|
+
### Added — `--self-healing` flag (scaffold-level integration)
|
|
426
|
+
|
|
427
|
+
Optional integration with the local `self_healing_stage_services` pipeline
|
|
428
|
+
([source](https://github.com/...)). When enabled, generated POMs register
|
|
429
|
+
every locator with the offline self-heal pipeline so the model knows which
|
|
430
|
+
locators each test references.
|
|
431
|
+
|
|
432
|
+
**Scope of v1.1.0** — registration + JSONL logging only. **Action-time
|
|
433
|
+
healing is v1.2** (see "Deferred" below).
|
|
434
|
+
|
|
435
|
+
#### What `--self-healing` does
|
|
436
|
+
|
|
437
|
+
- New CLI flag: `bdd2pw scaffold ... --self-healing` (off by default).
|
|
438
|
+
Programmatic API: `scaffold({ ..., selfHealing: true })`.
|
|
439
|
+
- Plumbs `selfHealingShim: true` to `@vijaypjavvadi/pw-emit`'s
|
|
440
|
+
`emitPageObject`. Emitted POMs now wrap every locator initialiser:
|
|
441
|
+
|
|
442
|
+
```ts
|
|
443
|
+
// Before (default):
|
|
444
|
+
this.usernameInput = page.getByLabel("Username");
|
|
445
|
+
|
|
446
|
+
// With --self-healing:
|
|
447
|
+
this.usernameInput = healOrThrow(page, {
|
|
448
|
+
preferred: page.getByLabel("Username"),
|
|
449
|
+
context: { page: "LoginPage", name: "usernameInput" },
|
|
450
|
+
});
|
|
451
|
+
```
|
|
452
|
+
- New scaffolder branch generates **`lib/heal.ts`** in the target repo —
|
|
453
|
+
a self-contained TypeScript helper that:
|
|
454
|
+
- Exports `healOrThrow(page, { preferred, context })` matching pw-emit's
|
|
455
|
+
contract.
|
|
456
|
+
- Registers every locator creation event to
|
|
457
|
+
**`artefacts/heal-events.jsonl`** for the offline self-heal pipeline
|
|
458
|
+
(`E:\EB1A_Research\self_healing_stage_services`) to consume.
|
|
459
|
+
- Optional fire-and-forget heartbeat to `${SELF_HEALING_URL}/api/v1/register`
|
|
460
|
+
when the env var is set. Best-effort — failures are silent.
|
|
461
|
+
- Honours `HEAL_DISABLE=1` for CI runs that don't want the artefact noise.
|
|
462
|
+
- Honours `HEAL_EVENTS_PATH` to override the JSONL path.
|
|
463
|
+
- Patches the generated `tsconfig.json` with a path alias:
|
|
464
|
+
```jsonc
|
|
465
|
+
"compilerOptions": {
|
|
466
|
+
"baseUrl": ".",
|
|
467
|
+
"paths": { "@platform/sdk-self-healing": ["./lib/heal"] }
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
so the POM's `import { healOrThrow } from "@platform/sdk-self-healing"`
|
|
471
|
+
resolves to the local helper. No external SDK dependency.
|
|
472
|
+
- Creates `artefacts/.gitkeep` so the JSONL output directory exists on a
|
|
473
|
+
fresh checkout.
|
|
474
|
+
|
|
475
|
+
#### What `--self-healing` does NOT do (v1.2 scope)
|
|
476
|
+
|
|
477
|
+
**Action-time healing.** When `await loginPage.usernameInput.click()` fails
|
|
478
|
+
because the locator no longer matches, this v1.1 helper will not catch the
|
|
479
|
+
failure, POST `/api/v1/heal`, and retry with the suggested locator. That
|
|
480
|
+
requires a Locator-wrapping proxy that intercepts every action method
|
|
481
|
+
(`.click`, `.fill`, `.check`, `.selectOption`, `.hover`, `.focus`,
|
|
482
|
+
`.textContent`, `.innerText`, `.getAttribute`, `.isVisible`,
|
|
483
|
+
`.waitForSelector`, ...).
|
|
484
|
+
|
|
485
|
+
The existing self-healing SDK (`self_healing_stage_services/sdk/playwright/self_healing_page.js`)
|
|
486
|
+
does this — but for *string-selector*-based test code (`page.fill('#username', ...)`).
|
|
487
|
+
pw-emit's POMs use Locator objects, so the v1.2 wrapper has a different
|
|
488
|
+
shape. Tracked for v1.2.
|
|
489
|
+
|
|
490
|
+
In the meantime, the JSONL feed gives the offline self-heal pipeline visibility
|
|
491
|
+
into what locators each test references, which is the v1 ranker's input
|
|
492
|
+
anyway — so v1.1 is functionally useful even without action-time recovery.
|
|
493
|
+
|
|
494
|
+
#### Files
|
|
495
|
+
|
|
496
|
+
- New: `templates/heal.ts.tmpl` — the heal helper template.
|
|
497
|
+
- Modified: `src/types.ts`, `src/cli.ts`, `src/index.ts`,
|
|
498
|
+
`src/repo/projectScaffolder.ts`.
|
|
499
|
+
|
|
500
|
+
## [1.0.1] — 2026-05-04
|
|
501
|
+
|
|
502
|
+
### Fixed
|
|
503
|
+
|
|
504
|
+
- **New step-matcher rule 11a — `URL contains "X"`.** Steps like
|
|
505
|
+
`Then user redirected to dashboard (URL contains "/dashboard")` or
|
|
506
|
+
`Then URL contains "/foo?bar=baz"` now correctly emit
|
|
507
|
+
`await expect(page).toHaveURL(new RegExp("..."))`. Previously these fell
|
|
508
|
+
through to rule 11 (the greedy "redirected to ..." rule), which produced
|
|
509
|
+
a wrong assertion or dropped the step to `BDD_REVIEW.md` as a TODO.
|
|
510
|
+
- The captured URL fragment is **regex-escaped** before being wrapped in
|
|
511
|
+
`new RegExp(...)`, so metacharacters like `?`, `+`, `.`, `(`, `)`, `[`, `]`
|
|
512
|
+
in URLs (query strings, encoded paths) match literally.
|
|
513
|
+
- Rule order matters: 11a is tried **before** 11b (the redirect rule) because
|
|
514
|
+
11b's pattern is greedy and would otherwise swallow the parenthetical hint.
|
|
515
|
+
|
|
516
|
+
### Tests
|
|
517
|
+
|
|
518
|
+
- `tests/unit/stepMatcher.test.ts` — added two regression cases:
|
|
519
|
+
- Plain URL fragment (`/dashboard`) → exact `new RegExp("/dashboard")`.
|
|
520
|
+
- Fragment with regex metacharacter (`/search?q=test`) → `?` is escaped to
|
|
521
|
+
`\?` so the literal `?` matches.
|
|
522
|
+
|
|
523
|
+
### Docs
|
|
524
|
+
|
|
525
|
+
- Bumped rule count from 14 to 15 across `README.md`, `docs/SCOPE.md`,
|
|
526
|
+
`docs/STATUS.md`.
|
|
527
|
+
|
|
528
|
+
## [1.0.0] — 2026-05-03
|
|
529
|
+
|
|
530
|
+
First public npm release. Six phases shipped, two real-world fixtures locked,
|
|
531
|
+
101 / 101 tests green. See [`docs/STATUS.md`](./docs/STATUS.md) for the runtime
|
|
532
|
+
snapshot and [`docs/SCOPE.md`](./docs/SCOPE.md) §17 for the full
|
|
533
|
+
implementation-time decisions log.
|
|
534
|
+
|
|
535
|
+
### Headline
|
|
536
|
+
|
|
537
|
+
`feature in → ready-to-run Playwright tests out`. Rule-based
|
|
538
|
+
deterministic generation (no LLM in v1.0). CLI + HTTP service. Same code path
|
|
539
|
+
through both surfaces.
|
|
540
|
+
|
|
541
|
+
### Shipped in v1.0
|
|
542
|
+
|
|
543
|
+
- **Phase 0** — `@vijaypjavvadi/pw-emit@1.0.0` extracted; bdd2pw consumes it as
|
|
544
|
+
a regular dep (`^1.0.0`).
|
|
545
|
+
- **Phase 1a** — Gherkin parser (`@cucumber/gherkin`), repo scanner (ts-morph),
|
|
546
|
+
POM resolver (reuse / augment / create), file-snapshot discovery, locator
|
|
547
|
+
picker (priority chain + status-region special case), 14-rule step matcher,
|
|
548
|
+
emitter facade, end-to-end `scaffold()`. Locked against `practice-test-login`
|
|
549
|
+
(7/7 mapped, 0 warnings).
|
|
550
|
+
- **Phase 1b** — Live browser discovery via direct `playwright.chromium.launch()`
|
|
551
|
+
(chose direct Playwright over `@playwright/mcp` for simpler in-process control).
|
|
552
|
+
Status-region visibility-filter bypass.
|
|
553
|
+
- **Phase 1c** — Second real fixture `cms-login` (third-person dialect),
|
|
554
|
+
0 warnings first try. Subject-agnostic prefix locked.
|
|
555
|
+
- **Phase 2** — `analyze` (dry-run) + `update-pom` (append-only AST surgery via
|
|
556
|
+
ts-morph; hard-fails on missing POM). `BDD_REVIEW.md` writer. `tsc --noEmit`
|
|
557
|
+
validator (soft-fail).
|
|
558
|
+
- **Phase 3** — Express HTTP service `:4300`. Three async workers, ULID job
|
|
559
|
+
IDs, Zod request validation, Pino structured logs, archiver-streamed zip
|
|
560
|
+
artefacts. Per-job tmpdir contains blast radius for HTTP scaffold runs.
|
|
561
|
+
|
|
562
|
+
### CLI
|
|
563
|
+
|
|
564
|
+
```
|
|
565
|
+
bdd2pw scaffold <feature> --url <url> --page <Name> --repo <dir> [opts]
|
|
566
|
+
bdd2pw analyze <feature> --url <url> [opts]
|
|
567
|
+
bdd2pw update-pom --page <Name> --url <url> --repo <dir>
|
|
568
|
+
bdd2pw serve [--port 4300]
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### HTTP
|
|
572
|
+
|
|
573
|
+
`POST /scaffold | /analyze | /update-pom` → `202 + { jobId, links }`.
|
|
574
|
+
`GET /jobs/:id`, `GET /jobs/:id/artifact` (zip), `GET /jobs/:id/log`,
|
|
575
|
+
`GET /healthz`, `GET /readyz`, `GET /version`.
|
|
576
|
+
|
|
577
|
+
### Tests
|
|
578
|
+
|
|
579
|
+
101 / 101 across 10 Vitest files. Includes a full HTTP regression that asserts
|
|
580
|
+
the artefact response body starts with the ZIP magic bytes `PK\x03\x04`.
|
|
581
|
+
|
|
582
|
+
### Distribution
|
|
583
|
+
|
|
584
|
+
- npm: `@vijaypjavvadi/bdd2pw@1.0.0` (public, scoped, with provenance).
|
|
585
|
+
- GitHub: tag-triggered `release.yml` workflow auto-publishes on `vX.Y.Z` push.
|
|
586
|
+
- CI matrix: Ubuntu / macOS / Windows × Node 18 / 20 / 22.
|
|
587
|
+
|
|
588
|
+
### Deferred to v1.1+
|
|
589
|
+
|
|
590
|
+
- LLM fallback for vague steps (governance-routed). Real fixtures hit 100%
|
|
591
|
+
rule coverage so this was off the critical path.
|
|
592
|
+
- SQLite-backed job persistence (currently in-memory `Map`).
|
|
593
|
+
- Built-in `auth.setup.ts` generator. Today: bring your own `--storage-state`.
|
|
594
|
+
- Multi-page navigation discovery, `playwright-bdd` runtime mode, API mocking
|
|
595
|
+
scaffolds, visual regression — all post-v1.1.
|
|
596
|
+
|
|
597
|
+
### Earlier-phase notes (kept for history)
|
|
598
|
+
|
|
599
|
+
#### Phase 3 — HTTP service is real
|
|
600
|
+
|
|
601
|
+
The `POST /scaffold`, `POST /analyze`, `POST /update-pom` routes now actually do work. Same code path as the CLI; HTTP is a thin shell.
|
|
602
|
+
|
|
603
|
+
#### What landed
|
|
604
|
+
|
|
605
|
+
- **Workers in `src/http/routes.ts`** — three async `Promise` workers, one per `POST` endpoint. Each accepts the parsed Zod-validated body, calls the matching `scaffold()` / `analyze()` / `updatePom()` function from `src/index.ts`, and updates the in-memory job state through `running` → `completed` / `failed`.
|
|
606
|
+
- **Per-job artifact dir.** `POST /scaffold` overrides the caller-provided `repo` with `<os.tmpdir()>/bdd2pw-jobs/<jobId>/`. This contains the blast radius — the HTTP user can't write outside that sandbox even if they pass an unsafe path.
|
|
607
|
+
- **`streamArtifactZip()` in `src/http/artifacts.ts`** — uses `archiver` to zip the artifact dir on the fly, streaming directly to the response. Excludes `node_modules`, `.git`, `test-results`, `playwright-report`. Headers: `application/zip` + `Content-Disposition: attachment; filename="bdd2pw-<jobId>.zip"`.
|
|
608
|
+
- **`GET /jobs/:id/log`** — returns a plain-text dump of the job's warnings + errors. Real per-job pino log file tailing deferred to v1.1.
|
|
609
|
+
- **`GET /jobs/:id/artifact`** failure modes covered: `404 JobNotFound`, `409 ArtifactNotReady` (job still running or failed), `410 ArtifactExpired` (artifact dir already swept).
|
|
610
|
+
|
|
611
|
+
#### Tests
|
|
612
|
+
|
|
613
|
+
- `tests/e2e/httpWorker.test.ts` — spins up the real Express app on a random port, drives it via `fetch()`, polls for completion. 11 assertions across 4 describe blocks:
|
|
614
|
+
- `/healthz`, `/version`, `/readyz` respond correctly.
|
|
615
|
+
- `POST /scaffold` validates the body and returns 400 for malformed input (missing fields, bad URL).
|
|
616
|
+
- End-to-end: POST `/scaffold` with the practice-test-login fixture → poll `/jobs/:id` → reaches `completed` with no warnings → `GET /jobs/:id/artifact` returns a real zip (verified by the ZIP magic bytes `PK\x03\x04` at the start of the response body).
|
|
617
|
+
- `GET /jobs/:id/log` returns a text/plain dump containing the job ID and status.
|
|
618
|
+
|
|
619
|
+
#### Out of scope for v1.0 (deferred to v1.1+)
|
|
620
|
+
|
|
621
|
+
- **Multi-tenancy / auth** — handled at the gateway (`modern-automation-platform:3000`), bdd2pw HTTP is single-tenant per process. Same pattern as `sel2pw`.
|
|
622
|
+
- **Job queue + worker pool** — current model is one Promise per job, bounded by Node's event loop. SQLite-backed queue with `p-limit` on MCP browsers lands in v1.1.
|
|
623
|
+
- **Granular per-stage progress events** — workers emit `parsing` / `emitting` / `completed`, but not the full 9-stage progress chain from the architecture doc. Sufficient for the UI to show "running" → "done"; finer-grained progress is a polish task.
|
|
624
|
+
- **Real per-job log file** — `GET /jobs/:id/log` currently returns warnings/errors from the in-memory record. Pino multi-stream routing per job is v1.1.
|
|
625
|
+
|
|
626
|
+
### Phase 2 — `updatePom` ships, append-only by construction
|
|
627
|
+
|
|
628
|
+
Replaces the `NotImplementedError` stub. **The third CLI command is now real.** Re-scanning a page that already has a Page Object merges newly-discovered locators in — without ever touching what's already there.
|
|
629
|
+
|
|
630
|
+
#### Design choice: append-only over lock-blocks
|
|
631
|
+
|
|
632
|
+
The original SCOPE proposed `// __BDD2PW_LOCK__` markers around generated content. Switched to **append-only via AST surgery** because:
|
|
633
|
+
|
|
634
|
+
- No visible markers in user code (lock comments are noise in otherwise hand-readable files).
|
|
635
|
+
- Stronger guarantee: enforced by construction, not by comments the user could strip.
|
|
636
|
+
- No special-casing for hand-edits — if it's already there, it's preserved.
|
|
637
|
+
- Smaller blast radius: bugs in updatePom can only ADD wrong things, never destroy existing code.
|
|
638
|
+
|
|
639
|
+
Trade-off: stale fields (when the live page removes an element) are not auto-deleted. Intentional — staleness is recoverable, deletion is not.
|
|
640
|
+
|
|
641
|
+
#### Hard rules `updatePom` enforces
|
|
642
|
+
|
|
643
|
+
1. Never deletes any property, method, or import.
|
|
644
|
+
2. Never renames anything.
|
|
645
|
+
3. Never modifies any existing method body — hand-edits are preserved byte-identical.
|
|
646
|
+
4. Skips field-name collisions (existing field wins).
|
|
647
|
+
5. Method synthesis is opt-in and deferred to v1.1.
|
|
648
|
+
6. Hard-fails if the POM file doesn't exist (suggests `scaffold` first).
|
|
649
|
+
7. Hard-fails if the class name doesn't match `--page`.
|
|
650
|
+
|
|
651
|
+
#### Implementation
|
|
652
|
+
|
|
653
|
+
- **`updatePom()` in `src/index.ts`** — replaces the stub. Uses `ts-morph` to read the existing POM AST, computes the new-fields diff against the live scan, and uses `addProperty` + `addStatements` to splice new content into the existing class. Then `save()` writes back. No re-emit, no overwrite.
|
|
654
|
+
- **`renderLocatorExpr` imported from `@vijaypjavvadi/pw-emit`** so the appended constructor lines use the same locator format as `scaffold` would.
|
|
655
|
+
- Honours `--no-discovery` and `--snapshot-file` flags for offline/CI use, mirroring `scaffold`.
|
|
656
|
+
- Surfaces a `BDD_REVIEW.md` with diff stats: existing/discovered/new/skipped-collisions counts.
|
|
657
|
+
|
|
658
|
+
#### Tests
|
|
659
|
+
|
|
660
|
+
- `tests/e2e/updatePom.test.ts` — 11 assertions across 3 describe blocks. The critical cases:
|
|
661
|
+
- **Hand-edits survive byte-identical** (the must-not-regress invariant): hand-written comments, `console.log` lines, and entire method bodies stay untouched.
|
|
662
|
+
- **Custom helper methods survive** — anything outside the discovered IR is preserved.
|
|
663
|
+
- **Existing field collisions are skipped** — if the user already declared `usernameInput` with a `#username` selector, updatePom does NOT overwrite with whatever the live snapshot would pick.
|
|
664
|
+
- **Idempotent** — second invocation in a row adds zero fields.
|
|
665
|
+
- **Hard-fail on missing class** — points at `scaffold`.
|
|
666
|
+
|
|
667
|
+
### Phase 1c — second real-world fixture clean (selenium14/LoginCMS)
|
|
668
|
+
|
|
669
|
+
**Validated against `selenium14/AutomationFrameworkCucumberTestNG/src/test/resources/features/LoginCMS.feature` — third-party Cucumber dialect from a real OSS Selenium codebase, against `https://cms.anhtester.com/login`.** 0 warnings, 0 errors, every step mapped.
|
|
670
|
+
|
|
671
|
+
This fixture exercises:
|
|
672
|
+
- `User navigate to Login Page for Admin "URL"` — third-person subject + bare-text + quoted URL → `goto()`
|
|
673
|
+
- `user enter email "admin@example.com" password "123456"` — **compound input step** (one Gherkin step, two field/value pairs) → two `.fill()` lines emitted
|
|
674
|
+
- `click Login button` — no subject prefix → `loginButton.click()`
|
|
675
|
+
- `user is redirected to the Dashboard page` — third-person `is redirected to` → `toHaveURL(/Dashboard/)`
|
|
676
|
+
|
|
677
|
+
#### Bugs fixed this round
|
|
678
|
+
|
|
679
|
+
- **Step matcher: subject-agnostic rules.** All 13 rules now accept `I | user | User | the user | the User` as the subject prefix (was hard-coded `I`). Verb forms also accept singular AND plural conjugation (`click | clicks`, `enter | enters`, etc.). For rule 3 (click) the subject is now optional entirely (`click Login button` works without any subject).
|
|
680
|
+
- **Rule 1 (navigate): handles `navigate to <text> "<URL>"`.** Previous pattern only accepted `navigate to <target>`. New pattern: `(?:.+?\s+)?["']?<target>["']?` — the optional `<text> ` prefix is consumed, the quoted URL becomes the target.
|
|
681
|
+
- **Rule 11 (redirect): accepts `is redirected`.** Was `(should be |am )?redirected`; now `(should be |am |is )?redirected`.
|
|
682
|
+
- **Compound input rule (2c).** New rule for `SUBJ verb FIELD1 "V1" FIELD2 "V2" [...]` — extracts every (field, value) pair via a global regex pass and emits one `.fill()` per pair. Falls back to `null` (TODO) if any field doesn't resolve, so partial matches don't produce wrong code.
|
|
683
|
+
|
|
684
|
+
#### Type addition (small)
|
|
685
|
+
|
|
686
|
+
- **`StepBinding.customBody?: string`** — for compound steps that produce multiple TS statements per Gherkin step. The emitter writes `customBody` verbatim instead of synthesising a single line from `pomCall`/`assertion`. Existing rules unchanged; only the new compound rule uses this path.
|
|
687
|
+
|
|
688
|
+
#### Orchestrator bug fix
|
|
689
|
+
|
|
690
|
+
- **Background warnings now flow into `BDD_REVIEW.md`.** Previously only scenario-step warnings were collected; Background-step warnings were silently dropped. Now surfaced under `[Background]`.
|
|
691
|
+
|
|
692
|
+
### Phase 1b milestone — live discovery 7/7 against the real site
|
|
693
|
+
|
|
694
|
+
**Same `practice-test-login` feature that needed a hand-authored snapshot in Phase 1a now passes 7/7 with `--url` only — bdd2pw scans the live page itself.** No snapshot file, no hand-tuning, real Chromium against `https://practicetestautomation.com/practice-test-login/`.
|
|
695
|
+
|
|
696
|
+
#### Iteration arc (live-scan rounds)
|
|
697
|
+
|
|
698
|
+
| Round | Pass / Fail | Root cause + fix |
|
|
699
|
+
|---|---|---|
|
|
700
|
+
| 1 | 4 / 3 | Scanner missed `<div id="error">` (no role/testid/aria-label) → broaden selector list to include `[id*=error i]`, `[class*=alert i]`, etc. |
|
|
701
|
+
| 2 | 3 / 4 | Scanner picked up error div but field name was text-derived (`yourUsernameIsInvalid`) → step matcher's `findField("error", [...])` couldn't match it. Fix: in `synthFieldName`, when CSS id matches `STATUS_REGION_RE`, prefer id-derived name → field becomes `error`. Plus `isLikelyStatusRegion` bypasses the visibility filter (status banners are intentionally hidden until triggered). Plus implicit roles for `<button>`/`<a>`/`<input type=submit>` so picker emits `getByRole`. Plus dedup-by-locator-value (collapse `username` + `usernameInput` into one). |
|
|
702
|
+
| 3 | 3 / 4 | Field name was right (`error`) but locator was wrong: picker emitted `getByText("Your username is invalid!")` not `locator("#error")`. Volatile text content was winning over the stable id. Fix: locator picker skips the `getByText` branch when element matches `STATUS_REGION_RE` — text changes per scenario, id is the stable handle. |
|
|
703
|
+
| 4 | **7 / 0** | — |
|
|
704
|
+
|
|
705
|
+
#### Bugs fixed this round
|
|
706
|
+
|
|
707
|
+
- **`src/discovery/mcpClient.ts`:** broader selector list (`[id*=error]`, `[class*=alert]`, …); `implicitRole()` for native HTML form controls; `isLikelyStatusRegion()` visibility-filter bypass; `isVisible()` zero-size + display:none filter; tsconfig `/// <reference lib="dom" />` so the `page.evaluate()` callback type-checks.
|
|
708
|
+
- **`src/transformers/locatorPicker.ts`:** new `STATUS_REGION_RE`; `synthFieldName` priority 0 = id-derived for status regions (so `<div id="error">` → `error`, not `yourUsernameIsInvalid`); `getByText` branch skipped for status regions; `dedupeLocators` now collapses by `(api, args)` identity, keeping the longer field name.
|
|
709
|
+
|
|
710
|
+
#### What this proves
|
|
711
|
+
|
|
712
|
+
Three of the original Phase 1a iteration rounds were **snapshot accuracy** bugs (fictional ARIA roles, fictional placeholders). With Phase 1b they're gone — the scanner sees what's actually there. The four Phase 1b rounds were rule/picker bugs surfaced by real DOM data, all fixed. Net result: a **single CLI invocation** with `--url` produces a runnable spec from a Cucumber `.feature` file with zero hand-edits, zero snapshot authoring.
|
|
713
|
+
|
|
714
|
+
### Phase 1b — real browser-based page discovery (initial implementation, earlier this round)
|
|
715
|
+
|
|
716
|
+
Replaces the file-snapshot-only discovery with a real Chromium-launching scanner. Hand-authoring `snapshot.json` is no longer required for the common case; pass `--url` and bdd2pw scans the live page itself.
|
|
717
|
+
|
|
718
|
+
#### What landed
|
|
719
|
+
|
|
720
|
+
- **`scanPageWithBrowser()`** in `src/discovery/mcpClient.ts` — uses `playwright.chromium.launch()` directly. Walks the DOM with a curated selector list (inputs, buttons, links, selects, headings, anything with `[role]` / `[data-testid]` / `[aria-label]`), extracts per-element role / accessible name / label / placeholder / testId / text / best CSS selector, and returns a flat `ElementIR[]`-shaped list. Snapshot parser handles it without changes.
|
|
721
|
+
- **`playwright`** added as an `optionalDependencies` entry. Dynamic-imported in the scanner with a clear install-hint error if missing (`"npm install -D playwright && npx playwright install chromium"`).
|
|
722
|
+
- **Storage state support** — pass `--storage-state <path.json>` to scan an authenticated page; passed through to `browser.newContext({ storageState })`.
|
|
723
|
+
- **Headed mode** — `--headed` shows the browser window, useful for debugging snapshot accuracy.
|
|
724
|
+
- **Network-idle wait with fallback** — first attempt waits 5s for network idle (catches SPAs that finish loading lazily); falls back to `domcontentloaded` if idle never settles.
|
|
725
|
+
|
|
726
|
+
#### Removed framing
|
|
727
|
+
|
|
728
|
+
- Dropped `@playwright/mcp` from optional dependencies. **AQ-2 resolved:** MCP is an LLM-control protocol, not a programmatic browser API. We just need one URL → one snapshot, so direct `playwright` is one fewer process and one fewer protocol layer. The file `src/discovery/mcpClient.ts` keeps its name for now (rename is cosmetic, deferred).
|
|
729
|
+
|
|
730
|
+
#### Backward compatibility
|
|
731
|
+
|
|
732
|
+
- `--snapshot-file <path>` still works exactly as before. The orchestrator dispatches: snapshot file present → file path; otherwise → real browser. CI runs that don't want network access keep using pinned snapshot files (e.g. `examples/practice-test-login/snapshot.json`).
|
|
733
|
+
- `practiceTestLogin.test.ts` still uses the file-snapshot path so the regression test runs without network.
|
|
734
|
+
|
|
735
|
+
#### What this fixes
|
|
736
|
+
|
|
737
|
+
The 3-of-6 iteration rounds in Phase 1a milestone that were "snapshot accuracy" bugs (fictional ARIA roles, fictional placeholders, etc.) — those scenarios should now Just Work because the scanner sees the actual DOM.
|
|
738
|
+
|
|
739
|
+
### Phase 1a milestone — first end-to-end green run (earlier this round)
|
|
740
|
+
|
|
741
|
+
**The full rule-based pipeline produced a runnable Playwright spec from a real-world Cucumber `.feature` file, with zero hand-edits.** Validated against `https://practicetestautomation.com/practice-test-login/` — `npx playwright test --project=chromium` reports **7 passed (4.2s)**.
|
|
742
|
+
|
|
743
|
+
#### Scope
|
|
744
|
+
|
|
745
|
+
- Source feature: `examples/practice-test-login/login.feature` — 5 scenarios + 1 Scenario Outline (2 example rows) + Background = 7 generated `test()` blocks
|
|
746
|
+
- Discovery source: hand-authored `examples/practice-test-login/snapshot.json` (Phase 1a fallback; real `@playwright/mcp` lands in Phase 1b)
|
|
747
|
+
- Output target: `pages/login.page.ts`, `tests/login.spec.ts`, `playwright.config.ts`, `package.json`, `tsconfig.json`, `.gitignore`, `BDD_REVIEW.md`
|
|
748
|
+
|
|
749
|
+
#### Iteration arc that got us there
|
|
750
|
+
|
|
751
|
+
| Round | Pass / Fail | Root cause |
|
|
752
|
+
|---|---|---|
|
|
753
|
+
| 1 | 0 / 7 | Undeclared `loginPage` (facade emitted POM calls without instantiation) |
|
|
754
|
+
| 2 | 4 / 3 | Rule 3 lost role hint; rule 8 required "the" not "an"; word-order mismatch in rule 2 |
|
|
755
|
+
| 3 | 2 / 5 | Snapshot fictional ARIA roles (`role: "alert"` not on page) |
|
|
756
|
+
| 4 | 1 / 6 | Snapshot fictional placeholders (`placeholder: "Username"` not on page) |
|
|
757
|
+
| 5 | 6 / 1 | Rule 9 emitted `toHaveText` (exact); step said "containing" |
|
|
758
|
+
| 6 | **7 / 0** | (none) |
|
|
759
|
+
|
|
760
|
+
3 of 6 rounds were rule/code bugs. 3 were snapshot accuracy — exactly what real `@playwright/mcp` integration will eliminate (Phase 1b).
|
|
761
|
+
|
|
762
|
+
#### Bugs fixed this round (rule + code)
|
|
763
|
+
|
|
764
|
+
- **`emitTestFile` facade (`src/emitters/facade.ts`):** prepend `const <pageVar> = new <Class>(page);` to every test body and every hook body. Previously the spec emitted `await loginPage.goto()` without ever declaring `loginPage`.
|
|
765
|
+
- **`scaffold()` orchestrator (`src/index.ts`):** synthesise a `goto(): Promise<void>` POM method that calls `await this.page.goto(<url>)` when no goto exists. Without it, rule 1 emitted `await loginPage.goto()` against a method with no body.
|
|
766
|
+
- **POM file naming (`src/index.ts`):** use `pageObjectFileName` from `@vijaypjavvadi/pw-emit` instead of `${camelCase(opts.page)}.page.ts`. Was emitting `loginPage.page.ts`; now emits `login.page.ts`.
|
|
767
|
+
- **Reserved field-name filter (`src/index.ts`):** `page`, `context`, `browser` are now excluded from the "missing fields" set. Previously `loginPage.page.toHaveURL(...)` made `page` look like a missing field.
|
|
768
|
+
- **Step matcher rule 2b (`src/transformers/stepMatcher.ts`):** new — handles reversed word order `I enter <field> "<value>"`. Original rule expected `I enter "<value>" into <field>`.
|
|
769
|
+
- **Step matcher rule 3:** captures the explicit role suffix (`button` / `link` / `icon` / `tab`) and passes only that to `findField` so "click the login button" doesn't match `testLoginHeading`.
|
|
770
|
+
- **Step matcher rule 8:** accepts `the | an | a error message`, prefers `Alert > Error > Message`-suffixed fields.
|
|
771
|
+
- **Step matcher rule 9a:** new — `toContainText` semantics for `containing | that contains | with text`. Must run before rule 9b (exact text equality) which now also accepts `the | an | a`.
|
|
772
|
+
- **Step matcher rules 11–13:** new — redirect (`toHaveURL` regex), attribute check (`toHaveAttribute`), URL prefix/contains (`toHaveURL` anchored regex).
|
|
773
|
+
- **`findField` resolution order (`src/transformers/stepMatcher.ts`):** added a suffix-constrained pass — when preferredSuffixes are provided, only consider fields with those suffixes; if exactly one matches, use it. Stops "login" from matching `testLoginHeading` when there's a `submitButton` on the same page.
|
|
774
|
+
- **`synthFieldName` (`src/transformers/locatorPicker.ts`):** added `cssSelectorToName()` fallback. When no name/label/placeholder/text is available, derive the field name from the CSS id (`#username` → `usernameInput`) instead of the ugly `inputElement` placeholder.
|
|
775
|
+
|
|
776
|
+
#### Added
|
|
777
|
+
|
|
778
|
+
- `examples/practice-test-login/` — pinned regression fixture (login.feature + snapshot.json + README)
|
|
779
|
+
- `tests/e2e/practiceTestLogin.test.ts` — 22-assertion regression test for the full pipeline. Calls `scaffold()`, reads back POM + spec, asserts every binding pattern that should have landed.
|
|
780
|
+
- `vitest.config.ts` — `hookTimeout: 30_000` for E2E test cold-start reads.
|
|
781
|
+
|
|
782
|
+
### Added — Phase 1a (initial implementation, earlier this round)
|
|
783
|
+
- **Real Gherkin parser** — `src/parser/gherkinParser.ts`. Uses `@cucumber/gherkin`. Handles Background, Scenario, Scenario Outline + Examples, doc strings, data tables.
|
|
784
|
+
- **Step matcher rules** — `src/transformers/stepMatcher.ts`. 10 rules covering navigation, input, click, select, check/uncheck, visibility (positive + negative), text equality, URL, error message. Falls through to a warning when no rule matches.
|
|
785
|
+
- **Locator picker** — `src/transformers/locatorPicker.ts`. Priority chain `getByRole > getByLabel > getByPlaceholder > getByTestId > getByText > css > xpath`. Ambiguity flagged when rivals exist. Field-name synthesis with role-based suffixes (`usernameInput`, `signInButton`).
|
|
786
|
+
- **POM resolver** — `src/transformers/pomResolver.ts`. Pure decision: REUSE / AUGMENT / CREATE based on existing fields vs referenced fields.
|
|
787
|
+
- **Repo scanner** — `src/repo/repoScanner.ts`. `ts-morph`-based AST scan of `pages/*.page.ts`. Extracts class name, locator fields, methods (preserves bodies for AUGMENT mode).
|
|
788
|
+
- **Project scaffolder** — `src/repo/projectScaffolder.ts`. Delegates to `@vijaypjavvadi/pw-emit`'s `emitProject()`. Idempotent.
|
|
789
|
+
- **Page discovery (file-snapshot fallback)** — `src/discovery/mcpClient.ts`. Phase 1a reads a pre-captured JSON snapshot via `--snapshot-file`. Real `@playwright/mcp` integration scheduled for Phase 1b.
|
|
790
|
+
- **Snapshot parser** — `src/discovery/snapshotParser.ts`. Accepts both flat-array and tree-shaped snapshots; flattens to `ElementIR[]`.
|
|
791
|
+
- **Review report** — `src/reports/reviewReport.ts`. Writes `BDD_REVIEW.md` with severity sections (Errors / Warnings / Info).
|
|
792
|
+
- **TSC validator** — `src/validate/tscRunner.ts`. Runs `tsc --noEmit` in the target repo, parses diagnostics into `ReviewItem`s. Soft-fails with a warning if `tsc` isn't installed.
|
|
793
|
+
- **`scaffold()` end-to-end wired** — `src/index.ts`. Orchestrates: parse → scan repo → scaffold → discover → pick locators → match steps (provisional) → resolve POM → re-match against final POM → expand Scenario Outlines → emit POM + spec → tsc validate → review report.
|
|
794
|
+
- **`analyze()` working** — same pipeline minus emit/validate. Returns the plan.
|
|
795
|
+
- **CLI `--snapshot-file` flag** for the file-based discovery path.
|
|
796
|
+
- **Example fixture** `examples/login-feature/snapshot.json` — hand-authored a11y tree for end-to-end testing.
|
|
797
|
+
- **3 new unit test files**: `stepMatcher.test.ts`, `locatorPicker.test.ts`, `pomResolver.test.ts`.
|
|
798
|
+
|
|
799
|
+
### Still stubbed (next phases)
|
|
800
|
+
- `updatePom()` — needs lock-block enforcement (Phase 2)
|
|
801
|
+
- Real Playwright MCP integration (Phase 1b)
|
|
802
|
+
- HTTP worker that calls `scaffold()` (Phase 3)
|
|
803
|
+
- LLM fallback in stepMatcher (Phase 4)
|
|
804
|
+
|
|
805
|
+
### Added — Phase 0 (previous round)
|
|
806
|
+
- Repository scaffold: package.json, tsconfig, ESLint, Prettier, Vitest, TypeDoc, MIT LICENSE
|
|
807
|
+
- CI workflow (Node 18/20/22 × Ubuntu/macOS/Windows), Release workflow (tag-triggered npm publish with provenance)
|
|
808
|
+
- `.changeset` config for versioning
|
|
809
|
+
- `src/` skeleton: `cli.ts`, `server.ts`, `index.ts`, `types.ts`, `utils/{naming,logger}.ts`
|
|
810
|
+
- Module stubs (now most replaced by Phase 1a implementations)
|
|
811
|
+
- Project templates: `package.json.tmpl`, `playwright.config.ts.tmpl`, `tsconfig.json.tmpl`, `gitignore.tmpl`
|
|
812
|
+
- Example fixture: `examples/login-feature/login.feature`
|
|
813
|
+
- First passing unit test: `tests/unit/naming.test.ts`
|
|
814
|
+
- `@vijaypjavvadi/pw-emit` consumed via `file:../pw-emit`
|
|
815
|
+
- `src/emitters/facade.ts` rewritten to delegate to `pw-emit`
|
|
816
|
+
|
|
817
|
+
### Documents
|
|
818
|
+
- `README.md` — project orientation, decisions log, phasing
|
|
819
|
+
- `docs/SCOPE.md` — v1.0 scope, FR/NFR, CLI + HTTP surface
|
|
820
|
+
- `docs/ARCHITECTURE.md` — component diagram, sequence flows, contracts, error model
|
|
821
|
+
|
|
822
|
+
## [0.1.0] — TBD
|
|
823
|
+
|
|
824
|
+
Initial published placeholder. **No runtime functionality yet.** Phase 0 (extracting `@vijaypjavvadi/pw-emit` from `sel2pw`) blocks Phase 1 of this package.
|