@specific.dev/spectest 0.18.0 → 0.19.0
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 +1 -1
- package/src/mobile.ts +44 -12
package/package.json
CHANGED
package/src/mobile.ts
CHANGED
|
@@ -68,11 +68,17 @@ interface LocatorDesc {
|
|
|
68
68
|
regex?: { source: string; flags: string };
|
|
69
69
|
name?: string;
|
|
70
70
|
nameExact?: boolean;
|
|
71
|
+
/** Narrowing applied after resolution (`.first()`/`.last()`/`.nth(i)`) —
|
|
72
|
+
* the explicit escape hatch from strict mode. */
|
|
73
|
+
nth?: number | "first" | "last";
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
/** A lazy reference to a single element, resolved (with auto-wait) at the
|
|
74
77
|
* moment an action runs. Mirrors the select-then-act idiom shared by
|
|
75
|
-
* Playwright, Detox, and RN Testing Library
|
|
78
|
+
* Playwright, Detox, and RN Testing Library — including Playwright's
|
|
79
|
+
* STRICT mode: a locator matching multiple elements throws an
|
|
80
|
+
* element-listing error rather than acting on one of them. Narrow with
|
|
81
|
+
* `exact: true`, a testID, or {@link first}/{@link nth}. */
|
|
76
82
|
export interface MobileLocator {
|
|
77
83
|
/** Wait for the element to be visible (default 5s, `timeoutMs` overrides),
|
|
78
84
|
* then touch-tap its center. `durationMs` overrides the touch dwell. */
|
|
@@ -94,13 +100,23 @@ export interface MobileLocator {
|
|
|
94
100
|
/** The element's text content, wrapped. Waits (default 5s) for the
|
|
95
101
|
* element to become visible; throws if it never does. */
|
|
96
102
|
textContent(): Promise<Wrapped<string | null>>;
|
|
103
|
+
/** Narrow to the first resolved element — the explicit opt-out from
|
|
104
|
+
* strict mode when multiple matches are intentional. */
|
|
105
|
+
first(): MobileLocator;
|
|
106
|
+
/** Narrow to the last resolved element. */
|
|
107
|
+
last(): MobileLocator;
|
|
108
|
+
/** Narrow to the i-th resolved element (0-based). */
|
|
109
|
+
nth(index: number): MobileLocator;
|
|
97
110
|
}
|
|
98
111
|
|
|
99
|
-
/** Map a locator descriptor onto a playwright Locator
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
112
|
+
/** Map a locator descriptor onto a playwright Locator — STRICT, exactly
|
|
113
|
+
* like stock Playwright: a locator that resolves to multiple elements
|
|
114
|
+
* throws an element-listing "strict mode violation" at action time
|
|
115
|
+
* instead of silently picking one. (An earlier `.filter({visible})
|
|
116
|
+
* .first()` auto-pick deterministically tapped the WRONG element when
|
|
117
|
+
* case-insensitive substring matching made two leaves qualify —
|
|
118
|
+
* `getByText("Men")` also matches "Wo**men**". Loud beats lucky.)
|
|
119
|
+
* Disambiguate with `exact: true`, a testID, or `.first()`/`.nth(i)`. */
|
|
104
120
|
function pwLocator(page: Page, desc: LocatorDesc): Locator {
|
|
105
121
|
let base: Locator;
|
|
106
122
|
switch (desc.kind) {
|
|
@@ -126,18 +142,25 @@ function pwLocator(page: Page, desc: LocatorDesc): Locator {
|
|
|
126
142
|
);
|
|
127
143
|
break;
|
|
128
144
|
}
|
|
129
|
-
|
|
145
|
+
if (desc.nth === "first") return base.first();
|
|
146
|
+
if (desc.nth === "last") return base.last();
|
|
147
|
+
if (typeof desc.nth === "number") return base.nth(desc.nth);
|
|
148
|
+
return base;
|
|
130
149
|
}
|
|
131
150
|
|
|
132
151
|
/** Short human label for a descriptor, used in event descriptions. */
|
|
133
152
|
function descLabel(desc: LocatorDesc): string {
|
|
153
|
+
let base: string;
|
|
134
154
|
if (desc.kind === "text") {
|
|
135
|
-
|
|
155
|
+
base = desc.regex ? `text /${desc.regex.source}/` : `text ${JSON.stringify(desc.value)}`;
|
|
156
|
+
} else if (desc.kind === "role") {
|
|
157
|
+
base = desc.name ? `role ${desc.value} ${JSON.stringify(desc.name)}` : `role ${desc.value}`;
|
|
158
|
+
} else {
|
|
159
|
+
base = `${desc.kind} ${JSON.stringify(desc.value)}`;
|
|
136
160
|
}
|
|
137
|
-
if (desc.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return `${desc.kind} ${JSON.stringify(desc.value)}`;
|
|
161
|
+
if (desc.nth === "first" || desc.nth === "last") return `${base} .${desc.nth}()`;
|
|
162
|
+
if (typeof desc.nth === "number") return `${base} .nth(${desc.nth})`;
|
|
163
|
+
return base;
|
|
141
164
|
}
|
|
142
165
|
|
|
143
166
|
/** Default wait for a locator action's target to become visible. Override
|
|
@@ -223,6 +246,15 @@ function makeLocator(backend: MobileBackend, desc: LocatorDesc): MobileLocator {
|
|
|
223
246
|
pwLocator(page, desc).textContent({ timeout: DEFAULT_ACTION_TIMEOUT_MS }),
|
|
224
247
|
) as Promise<Wrapped<string | null>>;
|
|
225
248
|
},
|
|
249
|
+
first() {
|
|
250
|
+
return makeLocator(backend, { ...desc, nth: "first" });
|
|
251
|
+
},
|
|
252
|
+
last() {
|
|
253
|
+
return makeLocator(backend, { ...desc, nth: "last" });
|
|
254
|
+
},
|
|
255
|
+
nth(index: number) {
|
|
256
|
+
return makeLocator(backend, { ...desc, nth: index });
|
|
257
|
+
},
|
|
226
258
|
};
|
|
227
259
|
}
|
|
228
260
|
|