diff --git a/README.md b/README.md index f8bd6f3..f26d69d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A plugin/middleware system for Playwright locator actions — the one Playwright doesn't have. -Wrap `click`, `fill`, `waitFor` and friends with composable middleware, so your tests can be smart about *why* an action is slow or failing, without sprinkling `waitForSomething()` helpers through every test. +Wrap `click`, `fill`, `inputValue`, `waitFor` and friends with composable middleware, so your tests can be smart about *why* an action is slow or failing, without sprinkling `waitForSomething()` helpers through every test. ## Quick start @@ -523,7 +523,7 @@ configured pattern must match the nearby comment: ## How it works -`addPlugins` patches `Locator.prototype` (once per process), replacing `click`, `dblclick`, `fill`, `type`, `press`, `clear`, `blur`, `focus`, `hover` and `waitFor` with a dispatcher. The dispatcher looks up the plugin state stored on the action's page; if the page has plugins, it runs the middleware chain (each middleware calling `next()` until the original method runs); if not, it calls the original method directly. +`addPlugins` patches `Locator.prototype` (once per process), replacing `click`, `dblclick`, `fill`, `type`, `press`, `clear`, `blur`, `focus`, `hover`, `inputValue` and `waitFor` with a dispatcher. The dispatcher looks up the plugin state stored on the action's page; if the page has plugins, it runs the middleware chain (each middleware calling `next()` until the original method runs); if not, it calls the original method directly. To keep Playwright's HTML report pointing at *your test code* rather than plugin internals, it registers the plugin files with playwright-core's internal `setBoxedStackPrefixes` — the same mechanism Playwright uses to hide its own frames. This is the unofficial-API part of the hack; set `PLAYWRIGHT_PLUGIN_DEBUG=1` to disable it when debugging the plugins themselves. @@ -546,3 +546,5 @@ Extracted from the internal test infrastructure of the [iterate](https://github. ## License MIT + +`inputValue()` uses the same loading-aware wait when the input has not appeared yet, then returns its current value. It does not wait for a particular value; an explicit timeout still passes through unchanged. diff --git a/spec/spinner-waiter.spec.ts b/spec/spinner-waiter.spec.ts index e303413..6fcd229 100644 --- a/spec/spinner-waiter.spec.ts +++ b/spec/spinner-waiter.spec.ts @@ -1,6 +1,6 @@ import http from "node:http"; import type { AddressInfo } from "node:net"; -import { test as base, expect } from "@playwright/test"; +import { test as base, expect, selectors } from "@playwright/test"; import { addPlugins, defaultSelectors, spinnerWaiter, type Plugin } from "../src/index.ts"; const test = base.extend<{ slowMutationTimeout: number }>({ @@ -32,6 +32,28 @@ test("slow button succeeds when there's a spinner", async ({ page }) => { await page.getByText("work done").waitFor(); }); +test("inputValue waits for a loading composer and returns its value", async ({ page }) => { + await page.setContent(` +
Opening chat…
+ + `); + expect(await page.getByLabel("Message").inputValue()).toBe("About my edited note"); +}); + +test("inputValue preserves an explicit timeout and can read disabled inputs", async ({ page }) => { + await page.setContent(''); + expect(await page.getByLabel("Saved note").inputValue()).toBe("green apples"); + await page.setContent('Opening chat…
'); + // Explicit timeout must bypass spinner-waiter even while progress is visible. + await expect(page.getByLabel("Missing").inputValue({ timeout: 100 })).rejects.toThrow( + /Timeout 100ms exceeded/, + ); +}); + test("visible disabled button succeeds when there's a spinner", async ({ page }) => { await page.setContent(`