feat(waitForURL): add a new waitForURL api (#6010)

This commit is contained in:
Pavel Feldman 2021-03-31 12:51:22 +08:00 коммит произвёл GitHub
Родитель 98f1f715c5
Коммит 85ab1dc7a4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 268 добавлений и 1 удалений

Просмотреть файл

@ -1395,3 +1395,31 @@ be flaky. Use signals such as network events, selectors becoming visible and oth
- `timeout` <[float]>
A timeout to wait for
## async method: Frame.waitForURL
Waits for the frame to navigate to the given URL.
```js
await frame.click('a.delayed-navigation'); // Clicking the link will indirectly cause a navigation
await frame.waitForURL('**/target.html');
```
```java
frame.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
frame.waitForURL("**/target.html");
```
```python async
await frame.click("a.delayed-navigation") # clicking the link will indirectly cause a navigation
await frame.wait_for_url("**/target.html")
```
```python sync
frame.click("a.delayed-navigation") # clicking the link will indirectly cause a navigation
frame.wait_for_url("**/target.html")
```
### param: Frame.waitForURL.url = %%-wait-for-navigation-url-%%
### option: Frame.waitForURL.timeout = %%-navigation-timeout-%%
### option: Frame.waitForURL.waitUntil = %%-navigation-wait-until-%%

Просмотреть файл

@ -2274,6 +2274,7 @@ This setting will change the default maximum navigation time for the following m
* [`method: Page.reload`]
* [`method: Page.setContent`]
* [`method: Page.waitForNavigation`]
* [`method: Page.waitForURL`]
:::note
[`method: Page.setDefaultNavigationTimeout`] takes priority over [`method: Page.setDefaultTimeout`],
@ -3109,6 +3110,36 @@ Shortcut for main frame's [`method: Frame.waitForTimeout`].
A timeout to wait for
## async method: Page.waitForURL
Waits for the main frame to navigate to the given URL.
```js
await page.click('a.delayed-navigation'); // Clicking the link will indirectly cause a navigation
await page.waitForURL('**/target.html');
```
```java
page.click("a.delayed-navigation"); // Clicking the link will indirectly cause a navigation
page.waitForURL("**/target.html");
```
```python async
await page.click("a.delayed-navigation") # clicking the link will indirectly cause a navigation
await page.wait_for_url("**/target.html")
```
```python sync
page.click("a.delayed-navigation") # clicking the link will indirectly cause a navigation
page.wait_for_url("**/target.html")
```
Shortcut for main frame's [`method: Frame.waitForURL`].
### param: Page.waitForURL.url = %%-wait-for-navigation-url-%%
### option: Page.waitForURL.timeout = %%-navigation-timeout-%%
### option: Page.waitForURL.waitUntil = %%-navigation-wait-until-%%
## async method: Page.waitForWebSocket
* langs: csharp, java
- returns: <[WebSocket]>

Просмотреть файл

@ -159,6 +159,12 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
});
}
async waitForURL(url: URLMatch, options: { waitUntil?: LifecycleEvent, timeout?: number } = {}): Promise<void> {
if (urlMatches(this.url(), url))
return await this.waitForLoadState(options?.waitUntil, options);
await this.waitForNavigation({ url, ...options });
}
async frameElement(): Promise<ElementHandle> {
return this._wrapApiCall(this._apiName('frameElement'), async (channel: channels.FrameChannel) => {
return ElementHandle.from((await channel.frameElement()).element);

Просмотреть файл

@ -362,6 +362,10 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
return this._attributeToPage(() => this._mainFrame.waitForNavigation(options));
}
async waitForURL(url: URLMatch, options?: { waitUntil?: LifecycleEvent, timeout?: number }): Promise<void> {
return this._attributeToPage(() => this._mainFrame.waitForURL(url, options));
}
async waitForRequest(urlOrPredicate: string | RegExp | ((r: Request) => boolean), options: { timeout?: number } = {}): Promise<Request> {
return this._wrapApiCall('page.waitForRequest', async (channel: channels.PageChannel) => {
const predicate = (request: Request) => {

Просмотреть файл

@ -0,0 +1,133 @@
/**
* Copyright 2018 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { it, expect } from '../fixtures';
it('should work', async ({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(url => window.location.href = url, server.PREFIX + '/grid.html');
await page.waitForURL('**/grid.html');
});
it('should respect timeout', async ({page, server}) => {
const promise = page.waitForURL('**/frame.html', { timeout: 2500 });
await page.goto(server.EMPTY_PAGE);
const error = await promise.catch(e => e);
expect(error.message).toContain('page.waitForNavigation: Timeout 2500ms exceeded.');
});
it('should work with both domcontentloaded and load', async ({page, server}) => {
let response = null;
server.setRoute('/one-style.css', (req, res) => response = res);
const navigationPromise = page.goto(server.PREFIX + '/one-style.html');
const domContentLoadedPromise = page.waitForURL('**/one-style.html', { waitUntil: 'domcontentloaded' });
let bothFired = false;
const bothFiredPromise = Promise.all([
page.waitForURL('**/one-style.html', { waitUntil: 'load' }),
domContentLoadedPromise
]).then(() => bothFired = true);
await server.waitForRequest('/one-style.css');
await domContentLoadedPromise;
expect(bothFired).toBe(false);
response.end();
await bothFiredPromise;
await navigationPromise;
});
it('should work with clicking on anchor links', async ({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.setContent(`<a href='#foobar'>foobar</a>`);
await page.click('a');
await page.waitForURL('**/*#foobar');
});
it('should work with history.pushState()', async ({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.setContent(`
<a onclick='javascript:pushState()'>SPA</a>
<script>
function pushState() { history.pushState({}, '', 'wow.html') }
</script>
`);
await page.click('a');
await page.waitForURL('**/wow.html');
expect(page.url()).toBe(server.PREFIX + '/wow.html');
});
it('should work with history.replaceState()', async ({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.setContent(`
<a onclick='javascript:replaceState()'>SPA</a>
<script>
function replaceState() { history.replaceState({}, '', '/replaced.html') }
</script>
`);
await page.click('a');
await page.waitForURL('**/replaced.html');
expect(page.url()).toBe(server.PREFIX + '/replaced.html');
});
it('should work with DOM history.back()/history.forward()', async ({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.setContent(`
<a id=back onclick='javascript:goBack()'>back</a>
<a id=forward onclick='javascript:goForward()'>forward</a>
<script>
function goBack() { history.back(); }
function goForward() { history.forward(); }
history.pushState({}, '', '/first.html');
history.pushState({}, '', '/second.html');
</script>
`);
expect(page.url()).toBe(server.PREFIX + '/second.html');
await page.click('a#back');
await page.waitForURL('**/first.html');
expect(page.url()).toBe(server.PREFIX + '/first.html');
await page.click('a#forward');
await page.waitForURL('**/second.html');
expect(page.url()).toBe(server.PREFIX + '/second.html');
});
it('should work with url match for same document navigations', async ({page, server}) => {
await page.goto(server.EMPTY_PAGE);
let resolved = false;
const waitPromise = page.waitForURL(/third\.html/).then(() => resolved = true);
expect(resolved).toBe(false);
await page.evaluate(() => {
history.pushState({}, '', '/first.html');
});
expect(resolved).toBe(false);
await page.evaluate(() => {
history.pushState({}, '', '/second.html');
});
expect(resolved).toBe(false);
await page.evaluate(() => {
history.pushState({}, '', '/third.html');
});
await waitPromise;
expect(resolved).toBe(true);
});
it('should work on frame', async ({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html');
const frame = page.frames()[1];
await frame.evaluate(url => window.location.href = url, server.PREFIX + '/grid.html');
await frame.waitForURL('**/grid.html');
});

67
types/types.d.ts поставляемый
Просмотреть файл

@ -2531,6 +2531,7 @@ export interface Page {
* - [page.reload([options])](https://playwright.dev/docs/api/class-page#pagereloadoptions)
* - [page.setContent(html[, options])](https://playwright.dev/docs/api/class-page#pagesetcontenthtml-options)
* - [page.waitForNavigation([options])](https://playwright.dev/docs/api/class-page#pagewaitfornavigationoptions)
* - [page.waitForURL(url[, options])](https://playwright.dev/docs/api/class-page#pagewaitforurlurl-options)
*
* > NOTE:
* [page.setDefaultNavigationTimeout(timeout)](https://playwright.dev/docs/api/class-page#pagesetdefaultnavigationtimeouttimeout)
@ -3174,6 +3175,39 @@ export interface Page {
*/
waitForTimeout(timeout: number): Promise<void>;
/**
* Waits for the main frame to navigate to the given URL.
*
* ```js
* await page.click('a.delayed-navigation'); // Clicking the link will indirectly cause a navigation
* await page.waitForURL('**\/target.html');
* ```
*
* Shortcut for main frame's
* [frame.waitForURL(url[, options])](https://playwright.dev/docs/api/class-frame#framewaitforurlurl-options).
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
* @param options
*/
waitForURL(url: string|RegExp|((url: URL) => boolean), options?: {
/**
* Maximum operation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be
* changed by using the
* [browserContext.setDefaultNavigationTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browsercontextsetdefaultnavigationtimeouttimeout),
* [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browsercontextsetdefaulttimeouttimeout),
* [page.setDefaultNavigationTimeout(timeout)](https://playwright.dev/docs/api/class-page#pagesetdefaultnavigationtimeouttimeout)
* or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#pagesetdefaulttimeouttimeout) methods.
*/
timeout?: number;
/**
* When to consider operation succeeded, defaults to `load`. Events can be either:
* - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.
* - `'load'` - consider operation to be finished when the `load` event is fired.
* - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms.
*/
waitUntil?: "load"|"domcontentloaded"|"networkidle";
}): Promise<void>;
/**
* This method returns all of the dedicated [WebWorkers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API)
* associated with the page.
@ -4476,7 +4510,38 @@ export interface Frame {
* be flaky. Use signals such as network events, selectors becoming visible and others instead.
* @param timeout A timeout to wait for
*/
waitForTimeout(timeout: number): Promise<void>;}
waitForTimeout(timeout: number): Promise<void>;
/**
* Waits for the frame to navigate to the given URL.
*
* ```js
* await frame.click('a.delayed-navigation'); // Clicking the link will indirectly cause a navigation
* await frame.waitForURL('**\/target.html');
* ```
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation.
* @param options
*/
waitForURL(url: string|RegExp|((url: URL) => boolean), options?: {
/**
* Maximum operation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be
* changed by using the
* [browserContext.setDefaultNavigationTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browsercontextsetdefaultnavigationtimeouttimeout),
* [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browsercontextsetdefaulttimeouttimeout),
* [page.setDefaultNavigationTimeout(timeout)](https://playwright.dev/docs/api/class-page#pagesetdefaultnavigationtimeouttimeout)
* or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#pagesetdefaulttimeouttimeout) methods.
*/
timeout?: number;
/**
* When to consider operation succeeded, defaults to `load`. Events can be either:
* - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.
* - `'load'` - consider operation to be finished when the `load` event is fired.
* - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms.
*/
waitUntil?: "load"|"domcontentloaded"|"networkidle";
}): Promise<void>;}
/**
* - extends: [EventEmitter]