api(dotnet): specialize waitForEvent (#6761)

This commit is contained in:
Pavel Feldman 2021-05-26 15:11:31 -07:00 committed by GitHub
parent 3aa1471489
commit bb0e196b15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 128 additions and 64 deletions

View File

@ -132,7 +132,7 @@ print(page.evaluate("location.href"))
```
```csharp
var popup = await context.RunAndWaitForEventAsync(BrowserContextEvent.Page, async =>
var popup = await context.RunAndWaitForPageAsync(async =>
{
await page.ClickAsync("a");
});
@ -1194,9 +1194,8 @@ Optional handler function used to register a routing with [`method: BrowserConte
Optional handler function used to register a routing with [`method: BrowserContext.route`].
## async method: BrowserContext.waitForEvent
* langs: csharp, js, python
* langs: js, python
- alias-python: expect_event
- alias-csharp: RunAndWaitForEventAsync
- returns: <[any]>
Waits for event to fire and passes its value into the predicate function. Returns when the predicate returns truthy
@ -1226,7 +1225,7 @@ page = event_info.value
```
```csharp
var page = await context.RunAndWaitForEventAsync(ContextEvent.Page, async () =>
var page = await context.RunAndWaitForPageAsync(async () =>
{
await page.ClickAsync("button");
});
@ -1247,8 +1246,9 @@ Event name, same one would pass into `browserContext.on(event)`.
Either a predicate that receives an event or an options object. Optional.
## async method: BrowserContext.waitForPage
* langs: java, python
* langs: java, python, csharp
- alias-python: expect_page
- alias-csharp: RunAndWaitForPage
- returns: <[Page]>
Performs action and waits for a new [Page] to be created in the context. If predicate is provided, it passes
@ -1264,9 +1264,8 @@ Receives the [Page] object and resolves to truthy value when the waiting should
### option: BrowserContext.waitForPage.timeout = %%-wait-for-event-timeout-%%
## async method: BrowserContext.waitForEvent2
* langs: python, csharp
* langs: python
- alias-python: wait_for_event
- alias-csharp: WaitForEventAsync
- returns: <[any]>
:::note

View File

@ -49,7 +49,7 @@ path = download.path()
```
```csharp
var download = await page.RunAndWaitForEventAsync(PageEvent.Download, async () =>
var download = await page.RunAndWaitForDownloadAsync(async () =>
{
await page.ClickAsync("#downloadButton");
});

View File

@ -30,7 +30,7 @@ file_chooser.set_files("myfile.pdf")
```
```csharp
var fileChooser = await page.RunAndWaitForEventAsync(Page.FileChooser, async () =>
var fileChooser = await page.RunAndWaitForFileChooserAsync(async () =>
{
await page.ClickAsync("upload");
});

View File

@ -1387,6 +1387,7 @@ await frame.WaitForLoadStateAsync(); // Defaults to LoadState.Load
## async method: Frame.waitForNavigation
* langs:
* alias-python: expect_navigation
* alias-csharp: RunAndWaitForNavigation
- returns: <[null]|[Response]>
Waits for the frame navigation and returns the main resource response. In case of multiple redirects, the navigation

View File

@ -266,7 +266,7 @@ try {
// Crash might happen during a click.
await page.ClickAsync("button");
// Or while waiting for an event.
await page.WaitForEventAsync(PageEvent.Popup);
await page.WaitForPopup();
} catch (PlaywrightException e) {
// When the page crashes, exception message contains "crash".
}
@ -397,7 +397,7 @@ print(popup.evaluate("location.href"))
```
```csharp
var popup = await page.RunAndWaitForEventAsync(PageEvent.Popup, async () =>
var popup = await page.RunAndWaitForPopupAsync(async () =>
{
await page.EvaluateAsync("() => window.open('https://microsoft.com')");
});
@ -2933,13 +2933,14 @@ Performs action and waits for the Page to close.
### option: Page.waitForClose.timeout = %%-wait-for-event-timeout-%%
## async method: Page.waitForConsoleMessage
* langs: java, python
* langs: java, python, csharp
- alias-python: expect_console_message
- alias-csharp: RunAndWaitForConsoleMessage
- returns: <[ConsoleMessage]>
Performs action and waits for a [ConsoleMessage] to be logged by in the page. If predicate is provided, it passes
[ConsoleMessage] value into the `predicate` function and waits for `predicate(message)` to return a truthy value.
Will throw an error if the page is closed before the console event is fired.
Will throw an error if the page is closed before the [`event: Page.console`] event is fired.
### option: Page.waitForConsoleMessage.predicate =
- `predicate` <[function]\([ConsoleMessage]\):[boolean]>
@ -2949,8 +2950,9 @@ Receives the [ConsoleMessage] object and resolves to truthy value when the waiti
### option: Page.waitForConsoleMessage.timeout = %%-wait-for-event-timeout-%%
## async method: Page.waitForDownload
* langs: java, python
* langs: java, python, csharp
- alias-python: expect_download
- alias-csharp: RunAndWaitForDownload
- returns: <[Download]>
Performs action and waits for a new [Download]. If predicate is provided, it passes
@ -2965,9 +2967,8 @@ Receives the [Download] object and resolves to truthy value when the waiting sho
### option: Page.waitForDownload.timeout = %%-wait-for-event-timeout-%%
## async method: Page.waitForEvent
* langs: csharp, js, python
* langs: js, python
- alias-python: expect_event
- alias-csharp: RunAndWaitForEventAsync
- returns: <[any]>
Waits for event to fire and passes its value into the predicate function. Returns when the predicate returns truthy
@ -2992,13 +2993,6 @@ with page.expect_event("framenavigated") as event_info:
frame = event_info.value
```
```csharp
var frame = await page.RunAndWaitForEventAsync(PageEvent.FrameNavigated, async () =>
{
await page.ClickAsync("button");
}
```
### param: Page.waitForEvent.event = %%-wait-for-event-event-%%
### param: Page.waitForEvent.optionsOrPredicate
@ -3011,8 +3005,9 @@ var frame = await page.RunAndWaitForEventAsync(PageEvent.FrameNavigated, async (
Either a predicate that receives an event or an options object. Optional.
## async method: Page.waitForFileChooser
* langs: java, python
* langs: java, python, csharp
- alias-python: expect_file_chooser
- alias-csharp: RunAndWaitForFileChooser
- returns: <[FileChooser]>
Performs action and waits for a new [FileChooser] to be created. If predicate is provided, it passes
@ -3223,7 +3218,7 @@ print(popup.title()) # popup is ready to use.
```
```csharp
var popup = await page.RunAndWaitForEventAsync(PageEvent.Popup, async () =>
var popup = await page.RunAndWaitForPopupAsync(async () =>
{
await page.ClickAsync("button"); // click triggers the popup/
});
@ -3240,6 +3235,7 @@ Shortcut for main frame's [`method: Frame.waitForLoadState`].
## async method: Page.waitForNavigation
* langs:
* alias-python: expect_navigation
* alias-csharp: RunAndWaitForNavigation
- returns: <[null]|[Response]>
Waits for the main frame navigation and returns the main resource response. In case of multiple redirects, the navigation
@ -3296,8 +3292,9 @@ Shortcut for main frame's [`method: Frame.waitForNavigation`].
### option: Page.waitForNavigation.timeout = %%-navigation-timeout-%%
## async method: Page.waitForPopup
* langs: java, python
* langs: java, python, csharp
- alias-python: expect_popup
- alias-csharp: RunAndWaitForPopup
- returns: <[Page]>
Performs action and waits for a popup [Page]. If predicate is provided, it passes
@ -3314,6 +3311,7 @@ Receives the [Page] object and resolves to truthy value when the waiting should
## async method: Page.waitForRequest
* langs:
* alias-python: expect_request
* alias-csharp: RunAndWaitForRequest
- returns: <[Request]>
Waits for the matching request and returns it. See [waiting for event](./events.md#waiting-for-event) for more details about events.
@ -3387,6 +3385,8 @@ await Task.WhenAll(page.WaitForRequestAsync(r => "https://example.com".Equals(r.
await page.waitForRequest(request => request.url().searchParams.get('foo') === 'bar' && request.url().searchParams.get('foo2') === 'bar2');
```
### param: Page.waitForRequest.action = %%-csharp-wait-for-event-action-%%
### param: Page.waitForRequest.urlOrPredicate
- `urlOrPredicate` <[string]|[RegExp]|[function]\([Request]\):[boolean]>
@ -3404,9 +3404,29 @@ Request URL string, regex or predicate receiving [Request] object.
Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. The default value can be
changed by using the [`method: Page.setDefaultTimeout`] method.
## async method: Page.waitForRequestFinished
* langs: java, python, csharp
- alias-python: expect_request_finished
- alias-csharp: RunAndWaitForRequestFinished
- returns: <[Request]>
Performs action and waits for a [Request] to finish loading. If predicate is provided, it passes
[Request] value into the `predicate` function and waits for `predicate(request)` to return a truthy value.
Will throw an error if the page is closed before the [`event: Page.requestFinished`] event is fired.
### option: Page.waitForRequestFinished.predicate =
- `predicate` <[function]\([Request]\):[boolean]>
Receives the [ConsoleMessage] object and resolves to truthy value when the waiting should resolve.
### option: Page.waitForRequestFinished.timeout = %%-wait-for-event-timeout-%%
## async method: Page.waitForResponse
* langs:
* alias-python: expect_response
* alias-csharp: RunAndWaitForResponse
- returns: <[Response]>
Returns the matched response. See [waiting for event](./events.md#waiting-for-event) for more details about events.
@ -3480,6 +3500,8 @@ await Task.WhenAll(page.WaitForResponseAsync(r => "https://example.com".Equals(r
page.ClickAsync("button.triggers-response"));
```
### param: Page.waitForResponse.action = %%-csharp-wait-for-event-action-%%
### param: Page.waitForResponse.urlOrPredicate
- `urlOrPredicate` <[string]|[RegExp]|[function]\([Response]\):[boolean]>
@ -3688,7 +3710,9 @@ Shortcut for main frame's [`method: Frame.waitForURL`].
### option: Page.waitForURL.waitUntil = %%-navigation-wait-until-%%
## async method: Page.waitForWebSocket
* langs: java
* langs: java, python, csharp
- alias-python: expect_websocket
- alias-csharp: RunAndWaitForWebSocket
- returns: <[WebSocket]>
Performs action and waits for a new [WebSocket]. If predicate is provided, it passes
@ -3703,8 +3727,9 @@ Receives the [WebSocket] object and resolves to truthy value when the waiting sh
### option: Page.waitForWebSocket.timeout = %%-wait-for-event-timeout-%%
## async method: Page.waitForWorker
* langs: java, python
* langs: java, python, csharp
- alias-python: expect_worker
- alias-csharp: RunAndWaitForWorker
- returns: <[Worker]>
Performs action and waits for a new [Worker]. If predicate is provided, it passes
@ -3729,9 +3754,8 @@ This does not contain ServiceWorkers
:::
## async method: Page.waitForEvent2
* langs: python, csharp
* langs: python
- alias-python: wait_for_event
- alias-csharp: WaitForEventAsync
- returns: <[any]>
:::note

View File

@ -239,7 +239,7 @@ print(request.timing)
```
```csharp
var request = await page.RunAndWaitForEventAsync(PageEvent.RequestFinished, async () =>
var request = await page.RunAndWaitForRequestFinishedAsync(async () =>
{
await page.GotoAsync("https://www.microsoft.com");
});

View File

@ -6,3 +6,14 @@ Returns parsed request's body for `form-urlencoded` and JSON as a fallback if an
When the response is `application/x-www-form-urlencoded` then a key/value object of the values will be returned.
Otherwise it will be parsed as JSON.
### param: BrowserContext.waitForPage.action = %%-csharp-wait-for-event-action-%%
### param: Frame.waitForNavigation.action = %%-csharp-wait-for-event-action-%%
### param: Page.waitForConsoleMessage.action = %%-csharp-wait-for-event-action-%%
### param: Page.waitForDownload.action = %%-csharp-wait-for-event-action-%%
### param: Page.waitForFileChooser.action = %%-csharp-wait-for-event-action-%%
### param: Page.waitForPopup.action = %%-csharp-wait-for-event-action-%%
### param: Page.waitForRequestFinished.action = %%-csharp-wait-for-event-action-%%
### param: Page.waitForNavigation.action = %%-csharp-wait-for-event-action-%%
### param: Page.waitForWebSocket.action = %%-csharp-wait-for-event-action-%%
### param: Page.waitForWorker.action = %%-csharp-wait-for-event-action-%%

View File

@ -518,6 +518,12 @@ Specify screenshot type, defaults to `png`.
Callback that performs the action triggering the event.
## csharp-wait-for-event-action
* langs: csharp
- `action` <[Func<Task>]>
Action that triggers the event.
## python-select-options-element
* langs: python
- `element` <[ElementHandle]|[Array]<[ElementHandle]>>

View File

@ -92,7 +92,7 @@ popup.value.goto("https://wikipedia.org")
```
```csharp
var popup = await page.RunAndWaitForEventAsync(PageEvent.Popup, async =>
var popup = await page.RunAndWaitForPopupAsync(async =>
{
await page.EvaluateAsync("window.open()");
});

View File

@ -750,7 +750,7 @@ file_chooser.set_files("myfile.pdf")
```
```csharp
var fileChooser = page.RunAndWaitForAsync(PageEvent.FileChooser, async () =>
var fileChooser = page.RunAndWaitForFileChooserAsync(async () =>
{
await page.ClickAsync("upload");
});

View File

@ -209,7 +209,7 @@ print(new_page.title())
```csharp
// Get page after a specific action (e.g. clicking a link)
var newPage = await context.RunAndWaitForEventAsync(BrowserContextEvent.Page, async () =>
var newPage = await context.RunAndWaitForPageAsync(async () =>
{
await page.ClickAsync("a[target='_blank']");
});
@ -311,7 +311,7 @@ print(popup.title())
```csharp
// Get popup after a specific action (e.g., click)
var newPage = await page.RunAndWaitForEventAsync(PageEvent.Popup, async () =>
var newPage = await page.RunAndWaitForPopupAsync(async () =>
{
await page.ClickAsync("#open");
});

View File

@ -434,7 +434,7 @@ popup.wait_for_load_state("load")
```
```csharp
var popup = await page.RunAndWaitForEventAsync(PageEvent.Popup, async () =>
var popup = await page.RunAndWaitForPopupAsync(async () =>
{
await page.ClickAsync("a[target='_blank']"); // Opens popup
});

View File

@ -244,7 +244,7 @@ popup = popup_info.value
```
```csharp
var popup = await page.RunAndWaitForEventAsync(PageEvent.Popup, async () =>
var popup = await page.RunAndWaitForPopupAsync(async () =>
{
await page.ClickAsync("#open");
});

View File

@ -58,10 +58,11 @@ const documentation = parseApi(path.join(PROJECT_DIR, 'docs', 'src', 'api'));
documentation.filterForLanguage('csharp');
documentation.setLinkRenderer(item => {
const asyncSuffix = item.member && item.member.async ? 'Async' : '';
if (item.clazz)
return `<see cref="I${toTitleCase(item.clazz.name)}"/>`;
else if (item.member)
return `<see cref="I${toTitleCase(item.member.clazz.name)}.${toMemberName(item.member)}"/>`;
return `<see cref="I${toTitleCase(item.member.clazz.name)}.${toMemberName(item.member)}${asyncSuffix}"/>`;
else if (item.option)
return `<paramref name="${item.option}"/>`;
else if (item.param)
@ -137,8 +138,11 @@ function renderClass(clazz) {
return;
const body = [];
for (const member of clazz.membersArray)
for (const member of clazz.membersArray) {
if (member.alias.startsWith('RunAnd'))
renderMember(member, clazz, { trimRunAndPrefix: true }, body);
renderMember(member, clazz, {}, body);
}
writeFile(
'public partial interface',
@ -169,8 +173,11 @@ function renderBaseClass(clazz) {
return;
const body = [];
for (const member of methodsWithOptions)
for (const member of methodsWithOptions) {
if (member.alias.startsWith('RunAnd'))
renderMethod(member, clazz, toMemberName(member), { mode: 'base', nodocs: true, public: true, trimRunAndPrefix: true }, body);
renderMethod(member, clazz, toMemberName(member), { mode: 'base', nodocs: true, public: true }, body);
}
writeFile(
'internal partial class',
@ -269,16 +276,14 @@ function toArgumentName(name) {
/**
* @param {Documentation.Member} member
* @param {{ omitAsync?: boolean; }=} options
*/
function toMemberName(member, options) {
function toMemberName(member, makeAsync = false) {
const assumedName = toTitleCase(member.alias || member.name);
if (member.kind === 'interface')
return `I${assumedName}`;
const omitAsync = options && options.omitAsync;
if (!omitAsync && member.kind === 'method' && member.async && !assumedName.endsWith('Async'))
return `${assumedName}Async`;
if (omitAsync && assumedName.endsWith('Async'))
if (makeAsync && member.async)
return assumedName + 'Async';
if (!makeAsync && assumedName.endsWith('Async'))
return assumedName.substring(0, assumedName.length - 'Async'.length);
return assumedName;
}
@ -317,13 +322,13 @@ function renderConstructors(name, type, out) {
*
* @param {Documentation.Member} member
* @param {Documentation.Class|Documentation.Type} parent
* @param {{nojson?: boolean}} options
* @param {{nojson?: boolean, trimRunAndPrefix?: boolean}} options
* @param {string[]} out
*/
function renderMember(member, parent, options, out) {
let name = toMemberName(member);
if (member.kind === 'method') {
renderMethod(member, parent, name, { mode: 'options' }, out);
renderMethod(member, parent, name, { mode: 'options', trimRunAndPrefix: options.trimRunAndPrefix }, out);
return;
}
@ -477,23 +482,24 @@ function generateEnumNameIfApplicable(type) {
* nodocs?: boolean,
* abstract?: boolean,
* public?: boolean,
* trimRunAndPrefix?: boolean,
* }} options
* @param {string[]} out
*/
function renderMethod(member, parent, name, options, out) {
// These are hard-coded in C#.
if (name.includes('WaitForEventAsync'))
return;
out.push('');
if (options.trimRunAndPrefix)
name = name.substring('RunAnd'.length);
/**
* @param {Documentation.Type} type
* @returns
*/
function resolveType(type) {
return translateType(type, parent, (t) => {
let newName = `${parent.name}${toMemberName(member, { omitAsync: true })}Result`;
documentedResults.set(newName, `Result of calling <see cref="I${toTitleCase(parent.name)}.${toMemberName(member)}"/>.`);
let newName = `${parent.name}${toMemberName(member)}Result`;
documentedResults.set(newName, `Result of calling <see cref="I${toTitleCase(parent.name)}.${toMemberName(member, true)}"/>.`);
return newName;
});
}
@ -595,9 +601,12 @@ function renderMethod(member, parent, name, options, out) {
* @param {Documentation.Member} arg
*/
function processArg(arg) {
if (options.trimRunAndPrefix && arg.name === 'action')
return;
if (arg.name === 'options') {
if (options.mode === 'options' || options.mode === 'base') {
const optionsType = member.clazz.name + toMemberName(member, { omitAsync: true }) + 'Options';
const optionsType = member.clazz.name + name + 'Options';
optionTypes.set(optionsType, arg.type);
args.push(`${optionsType} options = default`);
argTypeMap.set(`${optionsType} options = default`, 'options');
@ -661,6 +670,12 @@ function renderMethod(member, parent, name, options, out) {
pushArg(argType, argName, arg);
}
let modifiers = '';
if (options.abstract)
modifiers = 'protected abstract ';
if (options.public)
modifiers = 'public ';
member.argsArray
.sort((a, b) => b.alias === 'options' ? -1 : 0) //move options to the back to the arguments list
.forEach(processArg);
@ -670,6 +685,8 @@ function renderMethod(member, parent, name, options, out) {
// Generate options -> named transition.
const tokens = [];
for (const arg of member.argsArray) {
if (arg.name === 'action' && options.trimRunAndPrefix)
continue;
if (arg.name !== 'options') {
tokens.push(toArgumentName(arg.name));
continue;
@ -689,23 +706,17 @@ function renderMethod(member, parent, name, options, out) {
}
body = `
{
options ??= new ${member.clazz.name}${toMemberName(member, { omitAsync: true })}Options();
return ${name}(${tokens.join(', ')});
options ??= new ${member.clazz.name}${name}Options();
return ${toAsync(name, member.async)}(${tokens.join(', ')});
}`;
}
let modifiers = '';
if (options.abstract)
modifiers = 'protected abstract ';
if (options.public)
modifiers = 'public ';
if (!explodedArgs.length) {
if (!options.nodocs) {
out.push(...XmlDoc.renderXmlDoc(member.spec, maxDocumentationColumnWidth));
paramDocs.forEach((value, i) => printArgDoc(i, value, out));
}
out.push(`${modifiers}${type} ${name}(${args.join(', ')})${body}`);
out.push(`${modifiers}${type} ${toAsync(name, member.async)}(${args.join(', ')})${body}`);
} else {
let containsOptionalExplodedArgs = false;
explodedArgs.forEach((explodedArg, argIndex) => {
@ -727,7 +738,7 @@ function renderMethod(member, parent, name, options, out) {
overloadedArgs.push(arg);
}
}
out.push(`${modifiers}${type} ${name}(${overloadedArgs.join(', ')})${body}`);
out.push(`${modifiers}${type} ${toAsync(name, member.async)}(${overloadedArgs.join(', ')})${body}`);
if (argIndex < explodedArgs.length - 1)
out.push(''); // output a special blank line
});
@ -914,3 +925,15 @@ function printArgDoc(name, value, out) {
function toOverloadSuffix(typeName) {
return toTitleCase(typeName.replace(/[<].*[>]/, '').replace(/[^a-zA-Z]/g, ''));
}
/**
* @param {string} name
* @param {boolean} convert
*/
function toAsync(name, convert) {
if (!convert)
return name;
if (name.includes('<'))
return name.replace('<', 'Async<');
return name + 'Async';
}