Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,14 @@ class LocatorExpectation implements LocatorAssertions {
options: WaitOptions,
): Promise<void> {
const want = !this.negated;
const opts: WaitOptions = { ...options, sleep: (ms) => this.locator.driver.pause(ms) };
// Honour the locator's own timeout/interval (as its interactions do), then let the
// call-site options override — otherwise a locator built with a custom timeout has it
// respected by tap()/waitFor() but silently dropped by every expect(...) matcher.
const opts: WaitOptions = {
...this.locator.waitOptions,
...options,
sleep: (ms) => this.locator.driver.pause(ms),
};
const settled = await waitUntil(predicate, (value) => value === want, opts);
if (settled !== want) {
const not = this.negated ? ".not" : "";
Expand Down
9 changes: 9 additions & 0 deletions src/locator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,15 @@ export class Locator {
private readonly proximity?: { anchor: Locator; maxDistance?: number },
) {}

/**
* The wait options (timeout/interval) this locator was constructed with. Its own
* interactions merge these into every wait; exposing them lets `expect(locator)`
* honour the same per-locator timeout instead of falling back to the default.
*/
get waitOptions(): WaitOptions {
return this.options;
}

private attribute(): string {
return attributeFor(this.selector, this.driver.platform);
}
Expand Down
25 changes: 25 additions & 0 deletions test/locator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -955,3 +955,28 @@ test("getByRole { visible } uses Android displayed= and composes with other filt
/by\.role\("textfield", \{ name: "Nope", visible: true \}\)/,
);
});

test("expect(locator) honours the locator's own wait options, call-site overrides win", async () => {
const pauses: number[] = [];
const driver: Driver = {
platform: "android",
async source() {
return '<node text="Present" bounds="[0,0][10,10]" />';
},
async pause(ms) {
pauses.push(ms);
},
async tapAt() {},
};
// The locator carries its own short timeout + a distinctive interval; the element is
// absent, so the matcher polls until that timeout. Its interactions already honour
// these options — this proves expect(...) does too (it used the 250ms default before).
const loc = new Locator(driver, by.text("Absent"), { timeout: 25, interval: 7 });
await assert.rejects(() => expect(loc).toBeVisible(), /assertion not met/);
assert.ok(pauses.length > 0, "the matcher should have polled at least once");
assert.deepEqual([...new Set(pauses)], [7]); // every poll used the locator's 7ms interval

pauses.length = 0;
await assert.rejects(() => expect(loc).toBeVisible({ interval: 3, timeout: 25 }), /assertion not met/);
assert.deepEqual([...new Set(pauses)], [3]); // a call-site interval overrides the locator's
});