@sungen/driver-ui 3.2.13 → 3.2.14

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.13",
3
+ "version": "3.2.14",
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.13"
16
+ "@sun-asterisk/sungen": "3.2.14"
17
17
  },
18
18
  "peerDependencies": {
19
19
  "@playwright/test": "^1.57.0"
@@ -6,6 +6,22 @@ import { pathToRegexSource } from '../utils/url-path-regex';
6
6
  * Assertion patterns: visibility, text content, state, attributes
7
7
  * Uses template engine for framework-agnostic code generation
8
8
  */
9
+
10
+ /** State qualifiers that may follow `with {{value}}` and need value-filtered handling
11
+ * (see-with-variable-state). `is disabled`/`is hidden` are EXCLUDED — they keep their
12
+ * dedicated -disabled/-hidden twins. `is unchecked` is tested before `is checked` so
13
+ * the substring never misclassifies. `selected` probes at runtime (aria-selected vs
14
+ * checked) exactly like selected-assertion. */
15
+ function qualifiedState(text: string): { assertion: string; label: string; probe?: boolean } | null {
16
+ if (/\bis enabled\b/.test(text)) return { assertion: 'toBeEnabled()', label: 'enabled' };
17
+ if (/\bis unchecked\b/.test(text)) return { assertion: 'toBeChecked({ checked: false })', label: 'unchecked' };
18
+ if (/\bis checked\b/.test(text)) return { assertion: 'toBeChecked()', label: 'checked' };
19
+ if (/\bis focused\b/.test(text)) return { assertion: 'toBeFocused()', label: 'focused' };
20
+ if (/\bis empty\b/.test(text)) return { assertion: 'toBeEmpty()', label: 'empty' };
21
+ if (/\bis selected\b/.test(text)) return { assertion: '', label: 'selected', probe: true };
22
+ return null;
23
+ }
24
+
9
25
  export const assertionPatterns: StepPattern[] = [
10
26
  // Browser alert text assertion: "see [Are you sure?] alert"
11
27
  {
@@ -245,6 +261,60 @@ export const assertionPatterns: StepPattern[] = [
245
261
  return { code, comment: `Assert ${step.selectorRef} disabled with ${step.dataRef}` };
246
262
  },
247
263
  priority: 14,
264
+ },
265
+ // Pattern: Then User see [Time Slot] option with {{time}} is enabled|selected|checked|…
266
+ // The value-qualified STATE family. Mirrors see-with-variable-hidden/-disabled (both p14):
267
+ // without this twin the generic see-with-variable (p13) swallowed the step and emitted a
268
+ // bare toHaveText — the state qualifier was silently DROPPED, and on a multi-match
269
+ // `type: locator` selector (e.g. a 96-slot time picker) the unfiltered locator also
270
+ // strict-mode-failed. Filter by the EXACT value first, then assert the state.
271
+ // `is disabled` / `is hidden` keep their existing dedicated twins — not matched here.
272
+ {
273
+ name: 'see-with-variable-state',
274
+ matcher: (step: ParsedStep) =>
275
+ (step.text.includes('should see') || !!step.text.match(/\b(see|sees)\s+\[/)) &&
276
+ !!step.selectorRef &&
277
+ !!step.dataRef &&
278
+ step.text.includes('with') &&
279
+ qualifiedState(step.text) !== null,
280
+ generator: (step, context) => {
281
+ const state = qualifiedState(step.text)!;
282
+
283
+ let resolved: any = {};
284
+ try {
285
+ resolved = context.selectorResolver.resolveSelector(
286
+ step.selectorRef!,
287
+ context.featureName,
288
+ step.elementType,
289
+ step.nth
290
+ );
291
+ } catch (error) {}
292
+
293
+ let dataValue: string;
294
+ try {
295
+ dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
296
+ } catch (error) {
297
+ dataValue = `\${${step.dataRef}}`;
298
+ }
299
+
300
+ // "selected" mirrors selected-assertion: checkbox/radio/switch roles have no
301
+ // aria-selected — their state is `checked`; other targets probe at runtime.
302
+ let stateAssertion = state.assertion;
303
+ let selectedProbe = state.probe === true;
304
+ if (selectedProbe && resolved.role && ['checkbox', 'radio', 'switch'].includes(resolved.role)) {
305
+ stateAssertion = 'toBeChecked()';
306
+ selectedProbe = false;
307
+ }
308
+
309
+ const code = context.templateEngine.renderStep('state-with-filter-assertion', {
310
+ ...resolved,
311
+ dataValue,
312
+ stateAssertion,
313
+ selectedProbe,
314
+ });
315
+ return { code, comment: `Assert ${step.selectorRef} with ${step.dataRef} is ${state.label}` };
316
+ },
317
+ priority: 14,
248
318
  },
249
319
  // NEW: Assertion with variable (handles both empty selector and static + variable)
250
320
  // Pattern: Then User see [error] message with {{fail_message}}
@@ -366,6 +436,24 @@ export const assertionPatterns: StepPattern[] = [
366
436
  }
367
437
 
368
438
  // --- Everything else: toHaveText (exact full match) ---
439
+ // Locator-strategy LIST MEMBERS first: `see [X] item|option|cell with {{v}}` on a
440
+ // CSS-locator list (custom widgets with no role — e.g. a 96-slot time picker) means
441
+ // "a member whose text is v exists". A bare toHaveText on the multi-matching locator
442
+ // strict-mode-fails, so filter by the EXACT value then assert visible — the locator
443
+ // counterpart of the role-based branch below. Unique-element types (text/message/…)
444
+ // keep toHaveText: that is a VALUE assertion and must not be weakened to existence.
445
+ const LOCATOR_LIST_MEMBER_TYPES = new Set(['item', 'listitem', 'option', 'cell']);
446
+ if (resolved.strategy === 'locator' && step.elementType && LOCATOR_LIST_MEMBER_TYPES.has(step.elementType)) {
447
+ const code = context.templateEngine.renderStep('visible-filtered-assertion', {
448
+ ...resolved,
449
+ dataValue,
450
+ exact: true,
451
+ });
452
+ return {
453
+ code,
454
+ comment: `Assert ${step.selectorRef} with text ${step.dataRef} is present`,
455
+ };
456
+ }
369
457
  // Locator strategy
370
458
  if (resolved.strategy === 'locator') {
371
459
  const code = context.templateEngine.renderStep('have-text-assertion', {