@letsrunit/playwright 0.3.8 → 0.3.9

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": "@letsrunit/playwright",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "Playwright extensions and utilities for letsrunit",
5
5
  "keywords": [
6
6
  "testing",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "packageManager": "yarn@4.10.3",
46
46
  "dependencies": {
47
- "@letsrunit/utils": "0.3.8",
47
+ "@letsrunit/utils": "0.3.9",
48
48
  "@playwright/test": "^1.57.0",
49
49
  "case": "^1.6.3",
50
50
  "diff": "^8.0.3",
@@ -5,16 +5,18 @@ type LocatorOptions = Parameters<Page['locator']>[1];
5
5
  /**
6
6
  * Locates an element using Playwright selectors, with fallbacks.
7
7
  */
8
- export async function locator(page: Page, selector: string): Promise<Locator> {
8
+ export async function fuzzyLocator(page: Page, selector: string): Promise<Locator> {
9
9
  const primary = page.locator(selector).first();
10
10
  if (await primary.count()) return primary;
11
11
 
12
- return await tryRelaxNameToHasText(page, selector)
13
- || await tryTagInsteadOfRole(page, selector)
14
- || await tryRoleNameProximity(page, selector)
15
- || await tryFieldAlternative(page, selector)
16
- || await tryAsField(page, selector)
17
- || primary; // Nothing found, return the original locator (so caller can still wait/assert)
12
+ return (
13
+ (await tryRelaxNameToHasText(page, selector)) ||
14
+ (await tryTagInsteadOfRole(page, selector)) ||
15
+ (await tryRoleNameProximity(page, selector)) ||
16
+ (await tryFieldAlternative(page, selector)) ||
17
+ (await tryAsField(page, selector)) ||
18
+ primary
19
+ ); // Nothing found, return the original locator (so caller can still wait/assert)
18
20
  }
19
21
 
20
22
  async function firstMatch(page: Page, sel: string | string[], opts: LocatorOptions = {}): Promise<Locator | null> {
@@ -65,11 +67,13 @@ async function tryRoleNameProximity(page: Page, selector: string): Promise<Locat
65
67
  }
66
68
 
67
69
  // Try alternatives if field is not found
68
- // field="foo" → #foo > input
70
+ // field="foo" → #foo > input (only when name is a valid CSS identifier)
69
71
  async function tryFieldAlternative(page: Page, selector: string): Promise<Locator | null> {
70
72
  const matchField = selector.match(/^field="([^"]+)"i?$/i);
71
73
  if (!matchField) return null;
72
74
  const [, field] = matchField;
75
+ // Skip if the name contains characters invalid in a CSS ID selector
76
+ if (!/^[a-zA-Z0-9_-]+$/.test(field)) return null;
73
77
  return firstMatch(page, `#${field} > input`);
74
78
  }
75
79
 
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from './browser';
2
2
  export * from './field/index';
3
3
  export * from './format-html';
4
- export * from './locator';
4
+ export * from './fuzzy-locator';
5
5
  export * from './selector';
6
6
  export * from './snapshot';
7
7
  export * from './screenshot';
@@ -109,6 +109,14 @@ export const createFieldEngine = () => ({
109
109
  const ph = (el as HTMLInputElement).placeholder ?? el.getAttribute('placeholder');
110
110
  if (ph) texts.push(ph);
111
111
 
112
+ // 7) Adjacent sibling <label> (handles <input/><label>Text</label> pattern without for/id)
113
+ for (let sib = el.nextElementSibling; sib; sib = sib.nextElementSibling) {
114
+ if (sib.tagName === 'LABEL') { texts.push(sib.textContent ?? ''); break; }
115
+ }
116
+ for (let sib = el.previousElementSibling; sib; sib = sib.previousElementSibling) {
117
+ if (sib.tagName === 'LABEL') { texts.push(sib.textContent ?? ''); break; }
118
+ }
119
+
112
120
  return texts;
113
121
  }
114
122