From 1fc02e8823bddead6a82be551934dffd17c77bde Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Sat, 16 Jan 2021 14:37:13 -0800 Subject: [PATCH] docs: add dialogs and downloads docs (#5042) --- docs/src/actionability.md | 15 ++++++- docs/src/dialogs.md | 94 +++++++++++++++++++++++++++++++++++++++ docs/src/downloads.md | 77 ++++++++++++++++++++++++++++++++ docs/src/emulation.md | 2 +- docs/src/network.md | 62 -------------------------- 5 files changed, 186 insertions(+), 64 deletions(-) create mode 100644 docs/src/dialogs.md create mode 100644 docs/src/downloads.md diff --git a/docs/src/actionability.md b/docs/src/actionability.md index 79fba5e80d..3acbcf644f 100644 --- a/docs/src/actionability.md +++ b/docs/src/actionability.md @@ -32,7 +32,20 @@ Some actions like [`method: Page.click`] support `force` option that disables no | textContent | Yes | - | - | - | - | - | | type | Yes | - | - | - | - | - | -
+You can check the actionability state of the element using one of the following methods: + +- [`method: ElementHandle.isChecked`] +- [`method: ElementHandle.isDisabled`] +- [`method: ElementHandle.isEditable`] +- [`method: ElementHandle.isEnabled`] +- [`method: ElementHandle.isHidden`] +- [`method: ElementHandle.isVisible`] +- [`method: Page.isChecked`] +- [`method: Page.isDisabled`] +- [`method: Page.isEditable`] +- [`method: Page.isEnabled`] +- [`method: Page.isHidden`] +- [`method: Page.isVisible`] ### Visible diff --git a/docs/src/dialogs.md b/docs/src/dialogs.md new file mode 100644 index 0000000000..6e69c141fa --- /dev/null +++ b/docs/src/dialogs.md @@ -0,0 +1,94 @@ +--- +id: dialogs +title: "Dialogs" +--- + +Playwright can interact with the web page dialogs such as [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert), [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm), [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) as well as [`beforeunload`](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event) confirmation. + + + +## alert(), confirm(), prompt() dialogs + +You can register a dialog handler before the action that triggers the dialog to accept or decline it. + +```js +page.on('dialog', dialog => dialog.accept()); +await page.click('button'); +``` + +```python async +page.on("dialog", lambda dialog: dialog.accept()) +await page.click("button") +``` + +```python sync +page.on("dialog", lambda dialog: dialog.accept()) +page.click("button") +``` + +:::note +If your action, be it [`method: Page.click`], [`method: Page.evaluate`] or any other, results in a dialog, the action will stall until the dialog is handled. That's because dialogs in Web are modal and block further page execution until they are handled. +::: + +As a result, following snippet will never resolve: + +:::warn +WRONG! +::: + +```js +await page.click('button'); // Will hang here +page.on('dialog', dialog => dialog.accept()) +``` + +:::warn +WRONG! +::: + +```python async +await page.click("button") # Will hang here +page.on("dialog", lambda dialog: dialog.accept()) +``` + +```python sync +page.click("button") # Will hang here +page.on("dialog", lambda dialog: dialog.accept()) +``` + +#### API reference + +- [`Dialog`] +- [`method: Dialog.accept`] +- [`method: Dialog.dismiss`] + +## beforeunload dialog + +When [`method: Page.close`] is invoked with the truthy [`option: runBeforeUnload`] value, it page runs its unload handlers. This is the only case when [`method: Page.close`] does not wait for the page to actually close, because it might be that the page stays open in the end of the operation. + +You can register a dialog handler to handle the beforeunload dialog yourself: + +```js +page.on('dialog', async dialog => { + assert(dialog.type() === 'beforeunload'); + await dialog.dismiss(); +}); +await page.close({runBeforeUnload: true}); +``` + +```python async +async def handle_dialog(dialog): + assert dialog.type == 'beforeunload' + await dialog.dismiss() + +page.on('dialog', lambda: handle_dialog) +await page.close(run_before_unload=True) +``` + +```python sync +def handle_dialog(dialog): + assert dialog.type == 'beforeunload' + dialog.dismiss() + +page.on('dialog', lambda: handle_dialog) +page.close(run_before_unload=True) +``` diff --git a/docs/src/downloads.md b/docs/src/downloads.md new file mode 100644 index 0000000000..22ea7bdfe1 --- /dev/null +++ b/docs/src/downloads.md @@ -0,0 +1,77 @@ +--- +id: downloads +title: "Downloads" +--- + +:::note +For uploading files, see the [uploading files](./input.md#upload-files) section. +::: + +For every attachment downloaded by the page, [`event: Page.download`] event is emitted. If you create a browser context +with the [`option: acceptDownloads`] set, all these attachments are going to be downloaded into a temporary folder. You +can obtain the download url, file system path and payload stream using the [Download] object from the event. + +You can specify where to persist downloaded files using the [`option: downloadsPath`] option in [`method: BrowserType.launch`]. + +:::note +Unless [`option: downloadsPath`] is set, downloaded files are deleted when the browser context that produced them is closed. +::: + +Here is the simplest way to handle the file download: + +```js +const [ download ] = await Promise.all([ + // Start waiting for the download + page.waitForEvent('download'), + // Perform the action that initiates download + page.click('button#delayed-download') +]); +// Wait for the download process to complete +const path = await download.path(); +``` + +```python async +# Start waiting for the download +async with page.expect_download() as download_info: + # Perform the action that initiates download + await page.click("button#delayed-download") +download = await download_info.value +# Wait for the download process to complete +path = await download.path() +``` + +```python sync +# Start waiting for the download +with page.expect_download() as download_info: + # Perform the action that initiates download + page.click("button#delayed-download") +download = download_info.value +# Wait for the download process to complete +path = download.path() +``` + +#### Variations + +If you have no idea what initiates the download, you can still handle the event: + +```js +page.on('download', download => download.path().then(console.log)); +``` + +```python async +async def handle_download(download): + print(await download.path()) +page.on("download", handle_download) +``` + +```python sync +page.on("download", lambda download: print(download.path())) +``` + +Note that handling the event forks the control flow and makes script harder to follow. Your scenario might end while you +are downloading a file since your main control flow is not awaiting for this operation to resolve. + +#### API reference +- [Download] +- [`event: Page.download`] +- [`method: Page.waitForEvent`] diff --git a/docs/src/emulation.md b/docs/src/emulation.md index 3e9ebf1dc9..9f86457717 100644 --- a/docs/src/emulation.md +++ b/docs/src/emulation.md @@ -1,6 +1,6 @@ --- id: emulation -title: "Device and environment emulation" +title: "Emulation" --- Playwright allows overriding various parameters of the device where the browser is running: diff --git a/docs/src/network.md b/docs/src/network.md index 1559e65497..d6ea05cb5e 100644 --- a/docs/src/network.md +++ b/docs/src/network.md @@ -43,68 +43,6 @@ page.goto("https://example.com") #### API reference - [`method: Browser.newContext`] -
- -## Handle file downloads - -```js -const [ download ] = await Promise.all([ - page.waitForEvent('download'), // <-- start waiting for the download - page.click('button#delayed-download') // <-- perform the action that directly or indirectly initiates it -]); -const path = await download.path(); -``` - -```python async -# Start waiting for the download -async with page.expect_download() as download_info: - # Perform the action that directly or indirectly initiates it - await page.click("button#delayed-download") -download = await download_info.value -path = await download.path() -``` - -```python sync -# Start waiting for the download -with page.expect_download() as download_info: - # Perform the action that directly or indirectly initiates it - page.click("button#delayed-download") -download = download_info.value -path = download.path() -``` - -For every attachment downloaded by the page, [`event: Page.download`] event is emitted. If you create a browser context -with the [`option: acceptDownloads`] set, all these attachments are going to be downloaded into a temporary folder. You -can obtain the download url, file system path and payload stream using the [Download] object from the event. - -#### Variations - -If you have no idea what initiates the download, you can still handle the event: - -```js -page.on('download', download => download.path().then(console.log)); -``` - -```python async -async def handle_download(download): - print(await download.path()) -page.on("download", handle_download) -``` - -```python sync -page.on("download", lambda download: print(download.path())) -``` - -Note that handling the event forks the control flow and makes script harder to follow. Your scenario might end while you -are downloading a file since your main control flow is not awaiting for this operation to resolve. - -#### API reference -- [Download] -- [`event: Page.download`] -- [`method: Page.waitForEvent`] - -
- ## Network events You can monitor all the requests and responses: