@sungen/driver-ui 3.2.12-beta.9 → 3.2.13

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.12-beta.9",
3
+ "version": "3.2.13",
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.12-beta.9"
16
+ "@sun-asterisk/sungen": "3.2.13"
17
17
  },
18
18
  "peerDependencies": {
19
19
  "@playwright/test": "^1.57.0"
@@ -378,6 +378,26 @@ export const assertionPatterns: StepPattern[] = [
378
378
  };
379
379
  }
380
380
 
381
+ // List-member roles: `see [X] item with {{v}}` on a dynamic list/dropdown means
382
+ // "an item whose text is v exists" — filter the (many-matching) locator by the value
383
+ // then assert visible, mirroring click-element-with-text. Plain toHaveText on the bare
384
+ // getByRole('listitem') strict-mode-fails when many match. SCOPED to role-based
385
+ // list-member roles only (listitem/option/cell) — unique labelled elements keep
386
+ // toHaveText (value check), and locator-type lists are unchanged. `row` is excluded
387
+ // (table rows have dedicated existence patterns).
388
+ const LIST_MEMBER_ROLES = new Set(['listitem', 'option', 'cell']);
389
+ if (resolved.strategy === 'role' && resolved.role && LIST_MEMBER_ROLES.has(resolved.role)) {
390
+ const { name: _name, ...resolvedWithoutName } = resolved;
391
+ const code = context.templateEngine.renderStep('visible-filtered-assertion', {
392
+ ...resolvedWithoutName,
393
+ dataValue,
394
+ });
395
+ return {
396
+ code,
397
+ comment: `Assert ${step.selectorRef} with text ${step.dataRef} is present`,
398
+ };
399
+ }
400
+
381
401
  // Role-based selector
382
402
  if (resolved.strategy === 'role' && resolved.role) {
383
403
  const hasName = resolved.name && resolved.name.trim();
@@ -229,6 +229,42 @@ export const interactionPatterns: StepPattern[] = [
229
229
  },
230
230
  priority: 11,
231
231
  },
232
+ {
233
+ // `hover [X] item with {{v}}` — pick the element whose text matches the value, mirroring
234
+ // click-element-with-text. Without this, hover fell through to hover-element which IGNORED
235
+ // the value and hovered the first match (strict-mode / wrong element on dynamic lists).
236
+ name: 'hover-element-with-text',
237
+ matcher: (step: ParsedStep) =>
238
+ (step.text.includes('hovers') || step.text.match(/\bhover\b/))
239
+ && !!step.selectorRef
240
+ && !!(step.dataRef || step.value)
241
+ && step.text.includes('with'),
242
+ resolver: (step, context) => {
243
+ const resolved = context.selectorResolver.resolveSelector(
244
+ step.selectorRef!, undefined, step.elementType, step.nth
245
+ );
246
+
247
+ let dataValue: string;
248
+ if (step.value) {
249
+ dataValue = step.value;
250
+ } else {
251
+ try {
252
+ dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
253
+ } catch {
254
+ dataValue = `\${${step.dataRef}}`;
255
+ }
256
+ }
257
+
258
+ const { name: _name, ...resolvedWithoutName } = resolved;
259
+
260
+ return {
261
+ templateName: 'hover-element-with-text',
262
+ data: { ...resolvedWithoutName, dataValue },
263
+ comment: `Hover ${step.selectorRef} with text ${step.dataRef || step.value}`,
264
+ };
265
+ },
266
+ priority: 9, // above hover-element (8) so `with {{v}}` filters instead of hovering the first
267
+ },
232
268
  {
233
269
  name: 'hover-element',
234
270
  matcher: (step: ParsedStep) =>