@sungen/driver-ui 3.2.32 → 3.2.34

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sungen/driver-ui",
3
- "version": "3.2.32",
3
+ "version": "3.2.34",
4
4
  "description": "Sungen UI capability (Playwright) — step patterns, viewpoint gate, and phase hooks. Plugs into @sun-asterisk/sungen via the capability SPI.",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -13,7 +13,7 @@
13
13
  "author": "eqe team (engineer & quality) — Sun Asterisk",
14
14
  "license": "MIT",
15
15
  "dependencies": {
16
- "@sun-asterisk/sungen": "3.2.32"
16
+ "@sun-asterisk/sungen": "3.2.34"
17
17
  },
18
18
  "peerDependencies": {
19
19
  "@playwright/test": "^1.57.0"
@@ -65,11 +65,7 @@ export const assertionPatterns: StepPattern[] = [
65
65
  resolver: (step, context): StepTemplateData => {
66
66
  let dataValue = step.selectorRef || '';
67
67
  if (step.dataRef) {
68
- try {
69
- dataValue = context.dataResolver.resolveData(step.dataRef, context.featureName);
70
- } catch {
71
- dataValue = `\${${step.dataRef}}`;
72
- }
68
+ dataValue = context.dataResolver.resolveData(step.dataRef, context.featureName);
73
69
  }
74
70
  return {
75
71
  templateName: 'alert-text-assertion',
@@ -92,11 +88,7 @@ export const assertionPatterns: StepPattern[] = [
92
88
  (step.elementType === 'column' || step.elementType === 'columnheader'),
93
89
  resolver: (step, context): StepTemplateData => {
94
90
  let dataValue: string;
95
- try {
96
- dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
97
- } catch (error) {
98
- dataValue = `\${${step.dataRef}}`;
99
- }
91
+ dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
100
92
 
101
93
  // getByRole('row') includes header row as nth(0), so Gherkin nth=1 maps to locator nth(1)
102
94
  const rowNth = step.nth || 1;
@@ -138,6 +130,8 @@ export const assertionPatterns: StepPattern[] = [
138
130
  // step is a URL-only oracle, which a client-side router can satisfy for a few hundred
139
131
  // milliseconds before a guard bounces the user away (#662).
140
132
  let anchor: Record<string, unknown> | undefined;
133
+ // The page entry's `query: any` (#748) — set below, once the entry is known to BE a page.
134
+ let ignoreQuery = false;
141
135
 
142
136
  // If selector is present, extract path from selector's value attribute
143
137
  if (step.selectorRef) {
@@ -155,6 +149,10 @@ export const assertionPatterns: StepPattern[] = [
155
149
  // the feature path rather than compiling `toHaveURL(/button/)`.
156
150
  path = resolved.selectorType === 'page' ? (resolved.value || fallbackPath) : fallbackPath;
157
151
  if (resolved.selectorType === 'page' && resolved.anchor) anchor = { ...resolved.anchor };
152
+ // `query: any` — the query is not part of this page's identity (#748). Gated on the entry
153
+ // KIND for the same reason `value` and `anchor` are: a key collision can hand back a
154
+ // non-page entry, and reading a stray field off it would silently weaken the assertion.
155
+ if (resolved.selectorType === 'page' && resolved.query === 'any') ignoreQuery = true;
158
156
  } catch (error) {
159
157
  // Fallback to feature path if selector not found
160
158
  path = fallbackPath;
@@ -167,11 +165,7 @@ export const assertionPatterns: StepPattern[] = [
167
165
  // has asserted it since issue #464; this is the same assertion, so it asserts the same way.
168
166
  let query = '';
169
167
  if (step.dataRef) {
170
- try {
171
- query = context.dataResolver.resolveData(step.dataRef, context.featureName);
172
- } catch {
173
- query = `\${${step.dataRef}}`;
174
- }
168
+ query = context.dataResolver.resolveData(step.dataRef, context.featureName);
175
169
  }
176
170
  // Convert path to regex-safe string (dynamic :id segments → [^/]+ wildcard). An ABSOLUTE
177
171
  // page value also yields a host pattern: matching only the path let this assertion pass on
@@ -183,7 +177,7 @@ export const assertionPatterns: StepPattern[] = [
183
177
  templateName: 'page-assertion',
184
178
  // `dataRef` rides along so a runtime failure can name the test-data key an author must go
185
179
  // fix — an empty/absent value is otherwise the hardest kind of red to trace.
186
- data: { pathRegex, hostRegex, query, dataRef: step.dataRef || '', anchor },
180
+ data: { pathRegex, hostRegex, query, dataRef: step.dataRef || '', anchor, ignoreQuery },
187
181
  comment: step.selectorRef ? `Assert on ${step.selectorRef} page${withSuffix}` : `Assert on page${withSuffix}`,
188
182
  };
189
183
  },
@@ -211,11 +205,7 @@ export const assertionPatterns: StepPattern[] = [
211
205
  } catch (error) {}
212
206
 
213
207
  let dataValue: string;
214
- try {
215
- dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
216
- } catch (error) {
217
- dataValue = `\${${step.dataRef}}`;
218
- }
208
+ dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
219
209
 
220
210
  const escapedVariable = dataValue.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&');
221
211
 
@@ -284,11 +274,7 @@ export const assertionPatterns: StepPattern[] = [
284
274
  } catch (error) {}
285
275
 
286
276
  let dataValue: string;
287
- try {
288
- dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
289
- } catch (error) {
290
- dataValue = `\${${step.dataRef}}`;
291
- }
277
+ dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
292
278
 
293
279
  const escapedVariable = dataValue.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&');
294
280
 
@@ -359,11 +345,7 @@ export const assertionPatterns: StepPattern[] = [
359
345
  } catch (error) {}
360
346
 
361
347
  let dataValue: string;
362
- try {
363
- dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
364
- } catch (error) {
365
- dataValue = `\${${step.dataRef}}`;
366
- }
348
+ dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
367
349
 
368
350
  // "selected" mirrors selected-assertion: checkbox/radio/switch roles have no
369
351
  // aria-selected — their state is `checked`; other targets probe at runtime.
@@ -419,11 +401,7 @@ export const assertionPatterns: StepPattern[] = [
419
401
 
420
402
  // Resolve data variable value
421
403
  let dataValue: string;
422
- try {
423
- dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
424
- } catch (error) {
425
- dataValue = `\${${step.dataRef}}`;
426
- }
404
+ dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
427
405
 
428
406
  // Resolve selector from YAML with feature context
429
407
  let resolved: any = {};
@@ -535,9 +513,15 @@ export const assertionPatterns: StepPattern[] = [
535
513
  const label = resolved.value !== undefined && resolved.value.trim() === ''
536
514
  ? undefined
537
515
  : step.selectorRef;
516
+ // The locator here is built from the CAPTION, not from `resolved` — the common case has no
517
+ // selectors.yaml entry at all, so `resolved` is `{}` and carries nothing. The scope
518
+ // therefore comes off the context, which `withParentScope` sets whether or not the entry
519
+ // lookup succeeded; without it `… in [Education Card 6] card` was dropped silently and the
520
+ // assertion read whichever card the page happened to render first (#747).
538
521
  const code = context.templateEngine.renderStep('label-value-assertion', {
539
522
  label,
540
523
  dataValue,
524
+ parentLocator: context.parentLocator,
541
525
  });
542
526
  return {
543
527
  code,
@@ -564,6 +548,9 @@ export const assertionPatterns: StepPattern[] = [
564
548
  name: hasName ? resolved.name : dataValue,
565
549
  exact: resolved.exact || false,
566
550
  nth: resolved.nth,
551
+ // Rebuilt field by field rather than spread from `resolved`, so the scope `withParentScope`
552
+ // stamped there never arrives — the label branch's twin defect (#747).
553
+ parentLocator: resolved.parentLocator ?? context.parentLocator,
567
554
  });
568
555
  return {
569
556
  code,
@@ -865,11 +852,7 @@ export const assertionPatterns: StepPattern[] = [
865
852
  resolver: (step, context): StepTemplateData => {
866
853
  // Resolve data reference to actual value
867
854
  let dataValue: string;
868
- try {
869
- dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
870
- } catch (error) {
871
- dataValue = `\${${step.dataRef}}`;
872
- }
855
+ dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
873
856
 
874
857
  return {
875
858
  templateName: 'visible-assertion',
@@ -895,13 +878,9 @@ export const assertionPatterns: StepPattern[] = [
895
878
  if (match) {
896
879
  expectedCount = parseInt(match[1]);
897
880
  } else if (step.dataRef) {
898
- try {
899
- const resolvedData = context.dataResolver.resolveData(step.dataRef, context.featureName);
900
- const parsed = parseInt(resolvedData);
901
- expectedCount = isNaN(parsed) ? 1 : parsed;
902
- } catch {
903
- expectedCount = `\${${step.dataRef}}`;
904
- }
881
+ const resolvedData = context.dataResolver.resolveData(step.dataRef, context.featureName);
882
+ const parsed = parseInt(resolvedData);
883
+ expectedCount = isNaN(parsed) ? 1 : parsed;
905
884
  }
906
885
 
907
886
  const resolved = context.selectorResolver.resolveSelector(step.selectorRef!, undefined, step.elementType, step.nth);
@@ -926,13 +905,9 @@ export const assertionPatterns: StepPattern[] = [
926
905
  expectedCount = parseInt(match[1]);
927
906
  } else if (step.dataRef) {
928
907
  // Resolve data reference: "count {{number_items}}"
929
- try {
930
- const resolvedData = context.dataResolver.resolveData(step.dataRef, context.featureName);
931
- const parsed = parseInt(resolvedData);
932
- expectedCount = isNaN(parsed) ? 1 : parsed;
933
- } catch {
934
- expectedCount = `\${${step.dataRef}}`;
935
- }
908
+ const resolvedData = context.dataResolver.resolveData(step.dataRef, context.featureName);
909
+ const parsed = parseInt(resolvedData);
910
+ expectedCount = isNaN(parsed) ? 1 : parsed;
936
911
  }
937
912
 
938
913
  const resolved = context.selectorResolver.resolveSelector(step.selectorRef!, undefined, step.elementType, step.nth);
@@ -20,11 +20,7 @@ export const formPatterns: StepPattern[] = [
20
20
 
21
21
  let fileName: string;
22
22
  if (step.dataRef) {
23
- try {
24
- fileName = context.dataResolver.resolveData(step.dataRef, context.featureName);
25
- } catch (error) {
26
- fileName = `\${${step.dataRef}}`;
27
- }
23
+ fileName = context.dataResolver.resolveData(step.dataRef, context.featureName);
28
24
  } else {
29
25
  fileName = step.value!;
30
26
  }
@@ -46,11 +42,7 @@ export const formPatterns: StepPattern[] = [
46
42
 
47
43
  let value: string;
48
44
  if (step.dataRef) {
49
- try {
50
- value = context.dataResolver.resolveData(step.dataRef, context.featureName);
51
- } catch (error) {
52
- value = `\${${step.dataRef}}`;
53
- }
45
+ value = context.dataResolver.resolveData(step.dataRef, context.featureName);
54
46
  } else {
55
47
  value = step.value!;
56
48
  }
@@ -169,11 +161,7 @@ export const formPatterns: StepPattern[] = [
169
161
 
170
162
  let value: string;
171
163
  if (step.dataRef) {
172
- try {
173
- value = context.dataResolver.resolveData(step.dataRef, context.featureName);
174
- } catch (error) {
175
- value = `\${${step.dataRef}}`;
176
- }
164
+ value = context.dataResolver.resolveData(step.dataRef, context.featureName);
177
165
  } else {
178
166
  value = step.value!;
179
167
  }
@@ -163,11 +163,7 @@ export const interactionPatterns: StepPattern[] = [
163
163
  !!step.dataRef,
164
164
  resolver: (step, context) => {
165
165
  let fillValue: string;
166
- try {
167
- fillValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
168
- } catch {
169
- fillValue = `\${${step.dataRef}}`;
170
- }
166
+ fillValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
171
167
  return {
172
168
  templateName: 'alert-fill-action',
173
169
  data: { fillValue },
@@ -221,11 +217,7 @@ export const interactionPatterns: StepPattern[] = [
221
217
  if (step.value) {
222
218
  dataValue = step.value;
223
219
  } else {
224
- try {
225
- dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
226
- } catch {
227
- dataValue = `\${${step.dataRef}}`;
228
- }
220
+ dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
229
221
  }
230
222
  const { name: _name, ...resolvedWithoutName } = resolved;
231
223
 
@@ -261,11 +253,7 @@ export const interactionPatterns: StepPattern[] = [
261
253
  }
262
254
 
263
255
  let dataValue: string;
264
- try {
265
- dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
266
- } catch (error) {
267
- dataValue = `\${${step.dataRef}}`;
268
- }
256
+ dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
269
257
 
270
258
  let actionMethod = 'click';
271
259
  if (step.text.includes('hover')) actionMethod = 'hover';
@@ -341,11 +329,7 @@ export const interactionPatterns: StepPattern[] = [
341
329
  if (step.value) {
342
330
  dataValue = step.value;
343
331
  } else {
344
- try {
345
- dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
346
- } catch {
347
- dataValue = `\${${step.dataRef}}`;
348
- }
332
+ dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
349
333
  }
350
334
 
351
335
  const { name: _name, ...resolvedWithoutName } = resolved;
@@ -412,11 +396,7 @@ export const interactionPatterns: StepPattern[] = [
412
396
  if (step.value) {
413
397
  dataValue = step.value;
414
398
  } else {
415
- try {
416
- dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
417
- } catch {
418
- dataValue = `\${${step.dataRef}}`;
419
- }
399
+ dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
420
400
  }
421
401
 
422
402
  const { name: _name, ...resolvedWithoutName } = resolved;
@@ -453,11 +433,7 @@ export const interactionPatterns: StepPattern[] = [
453
433
  (step.text.includes('clicks') || step.text.includes('click')) && !step.selectorRef && !!step.dataRef,
454
434
  resolver: (step, context) => {
455
435
  let value: string;
456
- try {
457
- value = context.dataResolver.resolveData(step.dataRef!, context.featureName);
458
- } catch (error) {
459
- value = `\${${step.dataRef}}`;
460
- }
436
+ value = context.dataResolver.resolveData(step.dataRef!, context.featureName);
461
437
 
462
438
  return {
463
439
  templateName: 'click-action',
@@ -503,11 +479,7 @@ export const interactionPatterns: StepPattern[] = [
503
479
  const role = elementKeywordToRole(step.text)!;
504
480
 
505
481
  let dataValue: string;
506
- try {
507
- dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
508
- } catch (error) {
509
- dataValue = `\${${step.dataRef}}`;
510
- }
482
+ dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
511
483
 
512
484
  const state = extractWaitState(step.text);
513
485
 
@@ -597,11 +569,7 @@ export const interactionPatterns: StepPattern[] = [
597
569
  const resolved = context.selectorResolver.resolveSelector(step.selectorRef!, undefined, step.elementType, step.nth);
598
570
 
599
571
  let dataValue: string;
600
- try {
601
- dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
602
- } catch (error) {
603
- dataValue = `\${${step.dataRef}}`;
604
- }
572
+ dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
605
573
 
606
574
  const state = extractWaitState(step.text);
607
575
 
@@ -44,6 +44,10 @@ export const navigationPatterns: StepPattern[] = [
44
44
  ? `/${step.selectorRef}/`
45
45
  : (context.featurePath || '/');
46
46
  let path = pathFallback;
47
+ // The page entry's `query: any` (#748) — the twin of the `see [X] page` reading. These two
48
+ // resolvers have drifted apart twice; a rule one honours and the other does not is exactly
49
+ // that drift, so it is read here on the same line as the path.
50
+ let ignoreQuery = false;
47
51
 
48
52
  if (step.selectorRef) {
49
53
  try {
@@ -57,6 +61,7 @@ export const navigationPatterns: StepPattern[] = [
57
61
  // documented fallback. These two resolvers have drifted apart twice already; keep them
58
62
  // spelling the same rule.
59
63
  path = resolved.selectorType === 'page' ? (resolved.value || pathFallback) : pathFallback;
64
+ if (resolved.selectorType === 'page' && resolved.query === 'any') ignoreQuery = true;
60
65
  } catch (error) {
61
66
  path = pathFallback;
62
67
  }
@@ -71,11 +76,7 @@ export const navigationPatterns: StepPattern[] = [
71
76
  // Runtime mode resolves to a __SUNGEN_TD$…$__ marker → `path + testData.get(...)`.
72
77
  let query = '';
73
78
  if (step.dataRef) {
74
- try {
75
- query = context.dataResolver.resolveData(step.dataRef, context.featureName);
76
- } catch {
77
- query = `\${${step.dataRef}}`;
78
- }
79
+ query = context.dataResolver.resolveData(step.dataRef, context.featureName);
79
80
  }
80
81
  const withSuffix = step.dataRef ? ` with ${step.dataRef}` : '';
81
82
 
@@ -91,7 +92,7 @@ export const navigationPatterns: StepPattern[] = [
91
92
  templateName: 'route-assertion',
92
93
  // `dataRef` rides along so a runtime failure names the test-data key (see the `see` twin).
93
94
  // An absolute page value also carries a host pattern — see urlToPatternParts.
94
- data: { ...urlToPatternParts(path), query, dataRef: step.dataRef || '' },
95
+ data: { ...urlToPatternParts(path), query, dataRef: step.dataRef || '', ignoreQuery },
95
96
  comment: step.selectorRef ? `Assert on ${step.selectorRef} page${withSuffix}` : `Assert page route`,
96
97
  };
97
98
  }
@@ -24,12 +24,11 @@ export function rowIndexExpr(
24
24
  const varMatch = rowToken.match(/^\{\{([^}]+)\}\}$/);
25
25
  if (!varMatch) return String(Number(rowToken) + offset);
26
26
 
27
- let expr: string;
28
- try {
29
- expr = context.dataResolver.resolveData(varMatch[1].trim(), context.featureName);
30
- } catch {
31
- expr = `\${${varMatch[1].trim()}}`;
32
- }
27
+ // No fallback to the placeholder text. This value is spliced RAW into `.nth({{{rowIndexExpr}}})`,
28
+ // so `${n}` landed as a bare expression outside any template literal — a syntax error that took
29
+ // the whole spec file at parse time, not just the step, and named nothing useful. The throw
30
+ // reaches StepMapper.mapStep and becomes SG-E038 against this step alone (#781).
31
+ const expr: string = context.dataResolver.resolveData(varMatch[1].trim(), context.featureName);
33
32
  if (/^\d+$/.test(expr)) return String(Number(expr) + offset); // inline-data mode
34
33
  if (offset === 0) return expr;
35
34
  return offset > 0 ? `${expr} + ${offset}` : `${expr} - ${-offset}`;