diff --git a/src/expect.ts b/src/expect.ts index c0d7722..4ff6e52 100644 --- a/src/expect.ts +++ b/src/expect.ts @@ -120,7 +120,14 @@ class LocatorExpectation implements LocatorAssertions { options: WaitOptions, ): Promise { 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" : ""; diff --git a/src/locator.ts b/src/locator.ts index c771fe3..f25b73a 100644 --- a/src/locator.ts +++ b/src/locator.ts @@ -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); } diff --git a/test/locator.test.ts b/test/locator.test.ts index 6210c15..f7dd67e 100644 --- a/test/locator.test.ts +++ b/test/locator.test.ts @@ -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 ''; + }, + 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 +});