diff --git a/docs/src/api/class-framelocator.md b/docs/src/api/class-framelocator.md index c90ff1fa19778..3abcb3a678f89 100644 --- a/docs/src/api/class-framelocator.md +++ b/docs/src/api/class-framelocator.md @@ -217,3 +217,144 @@ Returns locator to the n-th matching frame. It's zero based, `nth(0)` selects th ### param: FrameLocator.nth.index * since: v1.17 - `index` <[int]> + +## async method: FrameLocator.waitForFunction +* since: v1.41 +- returns: <[JSHandle]> + +Returns when the [`param: expression`] returns a truthy value. It resolves to a JSHandle of the truthy value. + +**Usage** + +The [`method: FrameLocator.waitForFunction`] can be used to observe viewport size change: + +```js +const { webkit } = require('playwright'); // Or 'chromium' or 'firefox'. + +(async () => { + const browser = await webkit.launch(); + const page = await browser.newPage(); + const watchDog = page.frameLocator('iframe').waitForFunction(() => window.innerWidth < 100); + await page.setViewportSize({ width: 50, height: 50 }); + await watchDog; + await browser.close(); +})(); +``` + +```java +import com.microsoft.playwright.*; + +public class Example { + public static void main(String[] args) { + try (Playwright playwright = Playwright.create()) { + BrowserType webkit = playwright.webkit(); + Browser browser = webkit.launch(); + Page page = browser.newPage(); + page.setViewportSize(50, 50); + page.frameLocator('iframe').waitForFunction("() => window.innerWidth < 100"); + browser.close(); + } + } +} +``` + +```python async +import asyncio +from playwright.async_api import async_playwright, Playwright + +async def run(playwright: Playwright): + webkit = playwright.webkit + browser = await webkit.launch() + page = await browser.new_page() + await page.evaluate("window.x = 0; setTimeout(() => { window.x = 100 }, 1000);") + await page.frame_locator('iframe').wait_for_function("() => window.x > 0") + await browser.close() + +async def main(): + async with async_playwright() as playwright: + await run(playwright) +asyncio.run(main()) +``` + +```python sync +from playwright.sync_api import sync_playwright, Playwright + +def run(playwright: Playwright): + webkit = playwright.webkit + browser = webkit.launch() + page = browser.new_page() + page.evaluate("window.x = 0; setTimeout(() => { window.x = 100 }, 1000);") + page.frame_locator('iframe').wait_for_function("() => window.x > 0") + browser.close() + +with sync_playwright() as playwright: + run(playwright) +``` + +```csharp +using Microsoft.Playwright; +using System.Threading.Tasks; + +class FrameExamples +{ + public static async Task WaitForFunction() + { + using var playwright = await Playwright.CreateAsync(); + await using var browser = await playwright.Webkit.LaunchAsync(); + var page = await browser.NewPageAsync(); + await page.SetViewportSizeAsync(50, 50); + await page.MainFrame.WaitForFunctionAsync("window.innerWidth < 100"); + } +} +``` + +To pass an argument to the predicate of [`method: FrameLocator.waitForFunction`] function: + +```js +const selector = '.foo'; +await page.frameLocator('iframe').waitForFunction(selector => !!document.querySelector(selector), selector); +``` + +```java +String selector = ".foo"; + page.frameLocator('iframe').waitForFunction("selector => !!document.querySelector(selector)", selector); +``` + +```python async +selector = ".foo" +await page.frame_locator('iframe').wait_for_function("selector => !!document.querySelector(selector)", selector) +``` + +```python sync +selector = ".foo" + page.frame_locator('iframe').wait_for_function("selector => !!document.querySelector(selector)", selector) +``` + +```csharp +var selector = ".foo"; +await page.frameLocator('iframe').WaitForFunctionAsync("selector => !!document.querySelector(selector)", selector); +``` + +### param: FrameLocator.waitForFunction.expression = %%-evaluate-expression-%% +* since: v1.41 + +### param: FrameLocator.waitForFunction.expression = %%-js-evaluate-pagefunction-%% +* since: v1.41 + +### param: FrameLocator.waitForFunction.arg +* since: v1.41 +- `arg` ?<[EvaluationArgument]> + +Optional argument to pass to [`param: expression`]. + +### option: FrameLocator.waitForFunction.polling = %%-js-python-wait-for-function-polling-%% +* since: v1.41 + +### option: FrameLocator.waitForFunction.polling = %%-csharp-java-wait-for-function-polling-%% +* since: v1.41 + +### option: FrameLocator.waitForFunction.timeout = %%-wait-for-function-timeout-%% +* since: v1.41 + +### option: FrameLocator.waitForFunction.timeout = %%-wait-for-function-timeout-js-%% +* since: v1.41 diff --git a/packages/playwright-core/src/client/locator.ts b/packages/playwright-core/src/client/locator.ts index d6b47037bbd21..cb13fe6930db9 100644 --- a/packages/playwright-core/src/client/locator.ts +++ b/packages/playwright-core/src/client/locator.ts @@ -21,7 +21,7 @@ import * as util from 'util'; import { asLocator, isString, monotonicTime } from '../utils'; import { ElementHandle } from './elementHandle'; import type { Frame } from './frame'; -import type { FilePayload, FrameExpectOptions, Rect, SelectOption, SelectOptionOptions, TimeoutOptions } from './types'; +import type { FilePayload, FrameExpectOptions, Rect, SelectOption, SelectOptionOptions, TimeoutOptions, WaitForFunctionOptions } from './types'; import { parseResult, serializeArgument } from './jsHandle'; import { escapeForTextSelector } from '../utils/isomorphic/stringUtils'; import type { ByRoleOptions } from '../utils/isomorphic/locatorUtils'; @@ -419,6 +419,10 @@ export class FrameLocator implements api.FrameLocator { nth(index: number): FrameLocator { return new FrameLocator(this._frame, this._frameSelector + ` >> nth=${index}`); } + + async waitForFunction(pageFunction: structs.PageFunction, arg?: Arg, options?: WaitForFunctionOptions): Promise> { + return this._frame.waitForFunction(pageFunction, arg, options); + } } let _testIdAttributeName: string = 'data-testid'; diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 31dc4798ed832..4738621bf82f1 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -17737,6 +17737,58 @@ export interface FrameLocator { * @param index */ nth(index: number): FrameLocator; + + /** + * Returns when the `pageFunction` returns a truthy value. It resolves to a JSHandle of the truthy value. + * + * **Usage** + * + * The + * [frameLocator.waitForFunction(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-framelocator#frame-locator-wait-for-function) + * can be used to observe viewport size change: + * + * ```js + * const { webkit } = require('playwright'); // Or 'chromium' or 'firefox'. + * + * (async () => { + * const browser = await webkit.launch(); + * const page = await browser.newPage(); + * const watchDog = page.frameLocator('iframe').waitForFunction(() => window.innerWidth < 100); + * await page.setViewportSize({ width: 50, height: 50 }); + * await watchDog; + * await browser.close(); + * })(); + * ``` + * + * To pass an argument to the predicate of + * [frameLocator.waitForFunction(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-framelocator#frame-locator-wait-for-function) + * function: + * + * ```js + * const selector = '.foo'; + * await page.frameLocator('iframe').waitForFunction(selector => !!document.querySelector(selector), selector); + * ``` + * + * @param pageFunction Function to be evaluated in the page context. + * @param arg Optional argument to pass to `pageFunction`. + * @param options + */ + waitForFunction(pageFunction: Function|string, arg?: EvaluationArgument, options?: { + /** + * If `polling` is `'raf'`, then `pageFunction` is constantly executed in `requestAnimationFrame` callback. If + * `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. + * Defaults to `raf`. + */ + polling?: number|"raf"; + + /** + * Maximum time to wait for in milliseconds. Defaults to `0` - no timeout. The default value can be changed via + * `actionTimeout` option in the config, or by using the + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods. + */ + timeout?: number; + }): Promise; } /** diff --git a/tests/page/locator-frame.spec.ts b/tests/page/locator-frame.spec.ts index 08da3dff0ab33..2748e97febe88 100644 --- a/tests/page/locator-frame.spec.ts +++ b/tests/page/locator-frame.spec.ts @@ -298,3 +298,11 @@ it('should work with COEP/COOP/CORP isolated iframe', async ({ page, server, bro await page.frameLocator('iframe').getByRole('button').click(); expect(await page.frames()[1].evaluate(() => window['__clicked'])).toBe(true); }); + +it('should wait until the function is resolved', async ({ page, server }) => { + await routeIframe(page); + await page.goto(server.EMPTY_PAGE); + let resolved = false; + await page.frameLocator('iframe').waitForFunction('window.innerWidth == 1280').then(() => resolved = true); + expect(resolved).toBe(true); +}); \ No newline at end of file